Index: tags/5.2.23/LA-Tools/Changes
===================================================================
--- tags/5.2.23/LA-Tools/Changes	(revision 2119)
+++ tags/5.2.23/LA-Tools/Changes	(revision 2119)
@@ -0,0 +1,6 @@
+Revision history for Perl extension Tools.
+
+0.01  Wed Feb 15 17:42:28 2017
+	- original version; created by h2xs 1.23 with options
+		-X tools
+
Index: tags/5.2.23/LA-Tools/Makefile.PL
===================================================================
--- tags/5.2.23/LA-Tools/Makefile.PL	(revision 2119)
+++ tags/5.2.23/LA-Tools/Makefile.PL	(revision 2119)
@@ -0,0 +1,19 @@
+use 5.020001;
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+    NAME              => 'LA::Tools',
+    VERSION           => '0.1', # finds $VERSION, requires EU::MM from perl >= 5.5
+    PREREQ_PM         => {
+        'LWP::UserAgent' => undef,
+        'JSON'           => undef,
+        'Getopt::Long'   => undef,
+        'Pod::Usage'     => undef,
+    }, # e.g., Module::Name => 1.1
+    ABSTRACT          => 'External Tools for LATMOS::Accounts', # retrieve abstract from module
+    AUTHOR            => 'Olivier Thauvin <olivier.thauvin@latmos.ipsl.fr>',
+    #LICENSE           => 'perl',
+    #Value must be from legacy list of licenses here
+    #http://search.cpan.org/perldoc?Module%3A%3ABuild%3A%3AAPI
+);
Index: tags/5.2.23/LA-Tools/README
===================================================================
--- tags/5.2.23/LA-Tools/README	(revision 2119)
+++ tags/5.2.23/LA-Tools/README	(revision 2119)
@@ -0,0 +1,40 @@
+Tools version 0.01
+==================
+
+The README is used to introduce the module and provide instructions on
+how to install the module, any machine dependencies it may have (for
+example C compilers and installed libraries) and any other information
+that should be provided before the module is installed.
+
+A README file is required for CPAN modules since CPAN extracts the
+README file from a module distribution so that people browsing the
+archive can use it get an idea of the modules uses. It is usually a
+good idea to provide version information here so that people can
+decide whether fixes for the module are worth downloading.
+
+INSTALLATION
+
+To install this module type the following:
+
+   perl Makefile.PL
+   make
+   make test
+   make install
+
+DEPENDENCIES
+
+This module requires these other modules and libraries:
+
+  blah blah blah
+
+COPYRIGHT AND LICENCE
+
+Put the correct copyright and licence information here.
+
+Copyright (C) 2017 by Olivier Thauvin
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.20.1 or,
+at your option, any later version of Perl 5 you may have available.
+
+
Index: tags/5.2.23/LA-Tools/bin/rla-sshkey
===================================================================
--- tags/5.2.23/LA-Tools/bin/rla-sshkey	(revision 2119)
+++ tags/5.2.23/LA-Tools/bin/rla-sshkey	(revision 2119)
@@ -0,0 +1,146 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use LWP::UserAgent;
+use JSON;
+
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    rla-sshkey
+
+=head1 SYNOPSIS
+
+    rla-sshkey -u http://comptes/
+
+=head1 DESCRIPTION
+
+Query existing ssh-key in Latmos-Accounts base and push the keys inside user's home.
+
+Before writing the tools checks:
+
+=over 4
+
+=item the user's home directory exists
+
+=item the home directory is really owned by the user
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item -u|--url http://...
+
+The url of the base Latmos-Accounts website
+
+=item -c|--create
+
+Only create .ssh/autorized_keys, dont modify it if it already exists
+
+=item -h|--help
+
+Display this help
+
+=back
+
+=cut
+
+GetOptions(
+    'u|url=s'  => \my $url,
+    'c|create' => \my $create,
+    'h|help'   => sub { pod2usage(0) },
+) or pod2usage(0);
+
+my $ua = LWP::UserAgent->new;
+$ua->timeout(10);
+
+$ua->ssl_opts(
+    verify_hostname => 0,
+    SSL_verify_mode => 0x00
+) if ($ua->can('ssl_opts'));
+
+my $json = JSON->new->utf8(1);
+
+my $response = $ua->post(
+    $url . '/remote/listing',
+    {
+        otype     => 'user',
+        q         => [ 'exported=1', 'sshPublicKey=*' ],
+        attribute => [ qw(sshPublicKey uidNumber gidNumber homeDirectory) ],
+    }
+);
+
+if (!$response->is_success) {
+    die('Cannot fetch url: ' . $response->status_line);
+}
+
+my $res = $response->decoded_content();
+
+my $var = $json->decode($res);
+
+foreach my $user (keys %{ $var }) {
+
+
+    my ($uid,$gid) = ($var->{$user}->{uidNumber}[0], $var->{$user}->{gidNumber}[0]);
+
+    my $home = $var->{$user}->{homeDirectory}[0];
+    my @keys = @{ $var->{$user}->{sshPublicKey} || [] };
+
+    my @stat = stat($home) or do {
+        warn "Cannot stat $user\'s home: $!, skiping\n";
+        next;
+    };
+    if( $stat[4] != $uid) {
+        warn "$user\'s home is owned by $stat[4] instead " . $var->{$user}->{uidNumber}[0] . ", skipping\n";
+        next;
+    }
+
+    if ($create && -f "$home/.ssh/authorized_keys") {
+        next;
+    }
+
+    my @curkeys;
+    if (open(my $handle, '<', "$home/.ssh/authorized_keys")) {
+        while (my $line = <$handle>) {
+            chomp($line);
+            push(@curkeys, $line);
+        }
+        close($handle);
+    }
+
+    my @newkeys;
+    foreach my $key (@keys) {
+        my $need = 1;
+        foreach my $curkey (@curkeys) {
+            if ($key eq $curkey) {
+                $need = 0;
+                last;
+            }
+        }
+        push(@newkeys, $key) if ($need);
+    }
+
+    @newkeys or next;
+
+    if (! -d "$home/.ssh") {
+        mkdir("$home/.ssh", 0700) or do {
+            warn "Cannot create $home/.ssh: $!\n";
+            next;
+        };
+	chown($uid, $gid, "$home/.ssh");	
+    }
+
+    if (open(my $handle, '>>', "$home/.ssh/authorized_keys")) {
+        print $handle "$_\n" foreach(@newkeys);
+        close($handle);
+	chown($uid, $gid, "$home/.ssh/authorized_keys");	
+    } else {
+        warn "Cannot open $home/.ssh/authorized_keys: $!\n";
+    }
+}
Index: tags/5.2.23/LA-Tools/t/01_pod.t
===================================================================
--- tags/5.2.23/LA-Tools/t/01_pod.t	(revision 2119)
+++ tags/5.2.23/LA-Tools/t/01_pod.t	(revision 2119)
@@ -0,0 +1,19 @@
+use strict;
+use warnings;
+use Test::More;
+use Test::Pod;
+
+eval "use Pod::Checker";
+plan skip_all => 'Pod::Checker required' if $@;
+
+my @pods = (
+    all_pod_files('blib', 'man'),
+);
+
+plan tests => scalar(@pods) * 2;
+
+foreach(@pods) {
+    my $errors = podchecker($_);
+    ok($errors >= 0, "File $_ contains pod");
+    ok($errors <= 0, "Check Pod of $_");
+}
Index: tags/5.2.23/LA-Tools/t/01_pod_coverage.t
===================================================================
--- tags/5.2.23/LA-Tools/t/01_pod_coverage.t	(revision 2119)
+++ tags/5.2.23/LA-Tools/t/01_pod_coverage.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+my $trustparents = { coverage_class => 'Pod::Coverage::CountParents' };
+
+eval "use Test::Pod::Coverage 1.00";
+plan skip_all => "Test::Pod::Coverage 1.00 required for testing POD coverage" if $@;
+
+all_pod_coverage_ok( $trustparents );
Index: tags/5.2.23/LA-Tools/t/01_scripts.t
===================================================================
--- tags/5.2.23/LA-Tools/t/01_scripts.t	(revision 2119)
+++ tags/5.2.23/LA-Tools/t/01_scripts.t	(revision 2119)
@@ -0,0 +1,12 @@
+use strict;
+use warnings;
+use Test::More;
+
+eval "use Test::Script";
+plan skip_all => 'Test::Script required' if $@;
+
+plan tests => scalar(@ARGV);
+
+foreach(@ARGV) {
+    script_compiles($_);
+}
Index: tags/5.2.23/LATMOS-Accounts-Web/Changes
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/Changes	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/Changes	(revision 2119)
@@ -0,0 +1,38 @@
+5.2.15
+    - Create object from std input file or CSV listing
+
+5.2.14
+    - Fix redirection after login
+    - Don't propose SSL auth if apache return NULL
+    - Don't propose SSL auth if id is found multiple times
+
+3.X.X
+    - the default filter to limit object listing look severals attributes, the
+      result is given via an AJAX call
+
+    - add SSL authentication support
+
+    - add filter on user's group page
+
+    - review object list page and icons (Hans)
+
+    - move attributes label from code to configuration file, allowing to change
+      them
+    - add a prossible description/hint to attribute
+
+2.0.0
+    - add support of nethost/netzone object
+
+    - add a "My" page in user page listing object they own
+
+    - add icons to quickly identify user's account status in list (temporary,
+      expired, unexported)
+
+    - add a department page in group page with a realistic view of the current
+      people in the department. The section appear only when group have
+      sutype=dpmt
+
+    - add link to objects when attributes have reference
+
+0.01  2009-05-12 03:23:51
+    - initial revision, generated by Catalyst
Index: tags/5.2.23/LATMOS-Accounts-Web/MANIFEST
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/MANIFEST	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/MANIFEST	(revision 2119)
@@ -0,0 +1,330 @@
+Changes
+MANIFEST
+MANIFEST.SKIP
+Makefile.PL
+README
+apache/proxy.conf
+etc/httpd/latmos-accounts-web.conf
+etc/init.d/latmos-accounts-web
+etc/latmos-accounts-web.yml
+latmos-accounts-web.spec.in
+lib/Catalyst/Authentication/Credential/La.pm
+lib/LATMOS/Accounts/Web.pm
+lib/LATMOS/Accounts/Web/Controller/About.pm
+lib/LATMOS/Accounts/Web/Controller/Ajax.pm
+lib/LATMOS/Accounts/Web/Controller/Aliases.pm
+lib/LATMOS/Accounts/Web/Controller/Create.pm
+lib/LATMOS/Accounts/Web/Controller/Groups.pm
+lib/LATMOS/Accounts/Web/Controller/Json.pm
+lib/LATMOS/Accounts/Web/Controller/Json/AccAge.pm
+lib/LATMOS/Accounts/Web/Controller/Json/DpmtRH.pm
+lib/LATMOS/Accounts/Web/Controller/Json/Employment.pm
+lib/LATMOS/Accounts/Web/Controller/Json/EmploymentByPeriod.pm
+lib/LATMOS/Accounts/Web/Controller/Json/Expire.pm
+lib/LATMOS/Accounts/Web/Controller/Json/FreeIp.pm
+lib/LATMOS/Accounts/Web/Controller/Json/Info.pm
+lib/LATMOS/Accounts/Web/Controller/Json/List.pm
+lib/LATMOS/Accounts/Web/Controller/Json/RefVal.pm
+lib/LATMOS/Accounts/Web/Controller/Json/Stat.pm
+lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm
+lib/LATMOS/Accounts/Web/Controller/Json/Tree.pm
+lib/LATMOS/Accounts/Web/Controller/Login.pm
+lib/LATMOS/Accounts/Web/Controller/Logout.pm
+lib/LATMOS/Accounts/Web/Controller/Logs.pm
+lib/LATMOS/Accounts/Web/Controller/NetHosts.pm
+lib/LATMOS/Accounts/Web/Controller/NetZones.pm
+lib/LATMOS/Accounts/Web/Controller/OAliases.pm
+lib/LATMOS/Accounts/Web/Controller/Remote.pm
+lib/LATMOS/Accounts/Web/Controller/Remote/AskPwReset.pm
+lib/LATMOS/Accounts/Web/Controller/Remote/ChangePw.pm
+lib/LATMOS/Accounts/Web/Controller/Remote/Listing.pm
+lib/LATMOS/Accounts/Web/Controller/Remote/Obj.pm
+lib/LATMOS/Accounts/Web/Controller/Remote/PwReset.pm
+lib/LATMOS/Accounts/Web/Controller/Remote/Search.pm
+lib/LATMOS/Accounts/Web/Controller/Remote/Write.pm
+lib/LATMOS/Accounts/Web/Controller/Root.pm
+lib/LATMOS/Accounts/Web/Controller/Rpc.pm
+lib/LATMOS/Accounts/Web/Controller/Search.pm
+lib/LATMOS/Accounts/Web/Controller/Search/Adv.pm
+lib/LATMOS/Accounts/Web/Controller/Services.pm
+lib/LATMOS/Accounts/Web/Controller/Sites.pm
+lib/LATMOS/Accounts/Web/Controller/Stat.pm
+lib/LATMOS/Accounts/Web/Controller/Summary/AccAge.pm
+lib/LATMOS/Accounts/Web/Controller/Summary/Departments.pm
+lib/LATMOS/Accounts/Web/Controller/Summary/Employment.pm
+lib/LATMOS/Accounts/Web/Controller/Summary/EmploymentByPeriod.pm
+lib/LATMOS/Accounts/Web/Controller/Summary/Expire.pm
+lib/LATMOS/Accounts/Web/Controller/Summary/Offices.pm
+lib/LATMOS/Accounts/Web/Controller/Summary/Tree.pm
+lib/LATMOS/Accounts/Web/Controller/Tools/CSV.pm
+lib/LATMOS/Accounts/Web/Controller/Tools/LoadNew.pm
+lib/LATMOS/Accounts/Web/Controller/Tools/LoadPasswd.pm
+lib/LATMOS/Accounts/Web/Controller/Users.pm
+lib/LATMOS/Accounts/Web/Controller/Users/Employment.pm
+lib/LATMOS/Accounts/Web/I18N/fr.po
+lib/LATMOS/Accounts/Web/I18N/messages.pot
+lib/LATMOS/Accounts/Web/Model/Accounts.pm
+lib/LATMOS/Accounts/Web/Model/AttrFilter.pm
+lib/LATMOS/Accounts/Web/Model/AttrForms.pm
+lib/LATMOS/Accounts/Web/Model/AttrFormsA.pm
+lib/LATMOS/Accounts/Web/View/Json.pm
+lib/LATMOS/Accounts/Web/View/TT.pm
+lib/LATMOS/Accounts/Web/View/TTpart.pm
+root/favicon.ico
+root/html/about/index.tt
+root/html/ajax/check_new_name.tt
+root/html/ajax/check_ug_validity.tt
+root/html/ajax/cracklib.tt
+root/html/ajax/displayattr.tt
+root/html/ajax/list_objects.tt
+root/html/ajax/objattrexist.tt
+root/html/ajax/objexist.tt
+root/html/ajax/random_password.tt
+root/html/ajax/rawattr.tt
+root/html/ajax/select_freeip_select.tt
+root/html/ajax/select_objects_list.tt
+root/html/ajax/set_attrs.tt
+root/html/ajax/user_to_group_form.tt
+root/html/aliases/default.tt
+root/html/aliases/index.tt
+root/html/component_disabled.tt
+root/html/create/default.tt
+root/html/default.tt
+root/html/groups/default.tt
+root/html/groups/dpmt.tt
+root/html/groups/index.tt
+root/html/groups/menu.tt
+root/html/groups/object_header.tt
+root/html/groups/users.tt
+root/html/includes/adv_filter.tt
+root/html/includes/aliases_list.tt
+root/html/includes/defs.tt
+root/html/includes/filter.tt
+root/html/includes/form.tt
+root/html/includes/list_objects.tt
+root/html/includes/object_header.tt
+root/html/includes/select_objects_list.tt
+root/html/includes/sidebar.tt
+root/html/includes/wrapper.tt
+root/html/index.tt
+root/html/login/index.tt
+root/html/logout.tt
+root/html/logout/index.tt
+root/html/logs/index.tt
+root/html/nethosts/default.tt
+root/html/nethosts/index.tt
+root/html/nethosts/menu.tt
+root/html/nethosts/object_header.tt
+root/html/netzones/default.tt
+root/html/netzones/index.tt
+root/html/netzones/menu.tt
+root/html/netzones/object_header.tt
+root/html/no_object.tt
+root/html/oaliases/Create.tt
+root/html/oaliases/Edit.tt
+root/html/oaliases/index.tt
+root/html/search/adv/index.tt
+root/html/search/index.tt
+root/html/services/default.tt
+root/html/services/index.tt
+root/html/services/menu.tt
+root/html/services/object_header.tt
+root/html/sites/default.tt
+root/html/sites/index.tt
+root/html/sites/menu.tt
+root/html/sites/object_header.tt
+root/html/stat/display.tt
+root/html/stat/index.tt
+root/html/summary/accage/index.tt
+root/html/summary/departments/index.tt
+root/html/summary/employment/index.tt
+root/html/summary/employmentbyperiod/index.tt
+root/html/summary/expire/index.tt
+root/html/summary/offices/index.tt
+root/html/summary/rh/index.tt
+root/html/summary/tree/index.tt
+root/html/tools/csv/index.tt
+root/html/tools/csv/parseCSV.tt
+root/html/tools/loadnew/index.tt
+root/html/tools/loadpasswd/index.tt
+root/html/users/Display.tt
+root/html/users/address.tt
+root/html/users/address_form.tt
+root/html/users/employment.tt
+root/html/users/groups.tt
+root/html/users/index.tt
+root/html/users/mail.tt
+root/html/users/my.tt
+root/html/users/object_header.tt
+root/html/users/passwd.tt
+root/static/images/add.png
+root/static/images/appointment-new.png
+root/static/images/arrow-right.png
+root/static/images/avatar-default.png
+root/static/images/btn_120x50_built.png
+root/static/images/btn_120x50_built_shadow.png
+root/static/images/btn_120x50_powered.png
+root/static/images/btn_120x50_powered_shadow.png
+root/static/images/btn_88x31_built.png
+root/static/images/btn_88x31_built_shadow.png
+root/static/images/btn_88x31_powered.png
+root/static/images/btn_88x31_powered_shadow.png
+root/static/images/catalyst_logo.png
+root/static/images/changes-prevent.png
+root/static/images/contact-new.png
+root/static/images/dialog-cancel.png
+root/static/images/dialog-ok-apply.png
+root/static/images/edit-delete.png
+root/static/images/edit-disable.png
+root/static/images/edit-enable.png
+root/static/images/editclear.png
+root/static/images/emblem-default.png
+root/static/images/emblem-favorite.png
+root/static/images/emblem-urgent.png
+root/static/images/find.png
+root/static/images/gnome-globe.png
+root/static/images/group_add.png
+root/static/images/group_conf.png
+root/static/images/group_del.png
+root/static/images/gtk-add.png
+root/static/images/gtk-delete.png
+root/static/images/gtk-find.png
+root/static/images/gtk-go-back-ltr.png
+root/static/images/gtk-go-back-rtl.png
+root/static/images/gtk-goto-first-ltr.png
+root/static/images/gtk-goto-first-rtl.png
+root/static/images/gtk-home.png
+root/static/images/gtk-jump-to-rtl.png
+root/static/images/icon_edit.png
+root/static/images/locked.png
+root/static/images/logviewer.png
+root/static/images/mail-mark-important.png
+root/static/images/mail-mark-junk.png
+root/static/images/mail-mark-unread-new.png
+root/static/images/mail-mark-unread.png
+root/static/images/mail-read.png
+root/static/images/mail_forward.png
+root/static/images/network-workgroup.png
+root/static/images/preferences-desktop-wallpaper.png
+root/static/images/process-stop.png
+root/static/images/service_conf.png
+root/static/images/site_conf.png
+root/static/images/system-users.png
+root/static/images/user-trash.png
+root/static/images/user_add.png
+root/static/images/user_conf.png
+root/static/images/user_del.png
+root/static/images/video-display.png
+root/static/images/view-calendar-day.png
+root/static/images/view-media-artist.png
+root/static/images/view-pim-contacts.png
+root/static/js/d3/LICENSE
+root/static/js/d3/d3.v3.js
+root/static/js/d3/d3.v3.min.js
+root/static/js/jqplot/jquery.jqplot.css
+root/static/js/jqplot/jquery.jqplot.js
+root/static/js/jqplot/plugins/jqplot.BezierCurveRenderer.js
+root/static/js/jqplot/plugins/jqplot.barRenderer.js
+root/static/js/jqplot/plugins/jqplot.blockRenderer.js
+root/static/js/jqplot/plugins/jqplot.bubbleRenderer.js
+root/static/js/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js
+root/static/js/jqplot/plugins/jqplot.canvasAxisTickRenderer.js
+root/static/js/jqplot/plugins/jqplot.canvasOverlay.js
+root/static/js/jqplot/plugins/jqplot.canvasTextRenderer.js
+root/static/js/jqplot/plugins/jqplot.categoryAxisRenderer.js
+root/static/js/jqplot/plugins/jqplot.ciParser.js
+root/static/js/jqplot/plugins/jqplot.cursor.js
+root/static/js/jqplot/plugins/jqplot.dateAxisRenderer.js
+root/static/js/jqplot/plugins/jqplot.donutRenderer.js
+root/static/js/jqplot/plugins/jqplot.dragable.js
+root/static/js/jqplot/plugins/jqplot.enhancedLegendRenderer.js
+root/static/js/jqplot/plugins/jqplot.funnelRenderer.js
+root/static/js/jqplot/plugins/jqplot.highlighter.js
+root/static/js/jqplot/plugins/jqplot.json2.js
+root/static/js/jqplot/plugins/jqplot.logAxisRenderer.js
+root/static/js/jqplot/plugins/jqplot.mekkoAxisRenderer.js
+root/static/js/jqplot/plugins/jqplot.mekkoRenderer.js
+root/static/js/jqplot/plugins/jqplot.meterGaugeRenderer.js
+root/static/js/jqplot/plugins/jqplot.mobile.js
+root/static/js/jqplot/plugins/jqplot.ohlcRenderer.js
+root/static/js/jqplot/plugins/jqplot.pieRenderer.js
+root/static/js/jqplot/plugins/jqplot.pointLabels.js
+root/static/js/jqplot/plugins/jqplot.pyramidAxisRenderer.js
+root/static/js/jqplot/plugins/jqplot.pyramidGridRenderer.js
+root/static/js/jqplot/plugins/jqplot.pyramidRenderer.js
+root/static/js/jqplot/plugins/jqplot.trendline.js
+root/static/js/jquery/external/jquery/jquery.js
+root/static/js/jquery/images/ui-bg_flat_55_999999_40x100.png
+root/static/js/jquery/images/ui-bg_flat_75_aaaaaa_40x100.png
+root/static/js/jquery/images/ui-bg_glass_45_0078ae_1x400.png
+root/static/js/jquery/images/ui-bg_glass_55_f8da4e_1x400.png
+root/static/js/jquery/images/ui-bg_glass_75_79c9ec_1x400.png
+root/static/js/jquery/images/ui-bg_gloss-wave_45_e14f1c_500x100.png
+root/static/js/jquery/images/ui-bg_gloss-wave_50_6eac2c_500x100.png
+root/static/js/jquery/images/ui-bg_gloss-wave_75_2191c0_500x100.png
+root/static/js/jquery/images/ui-bg_inset-hard_100_fcfdfd_1x100.png
+root/static/js/jquery/images/ui-icons_0078ae_256x240.png
+root/static/js/jquery/images/ui-icons_056b93_256x240.png
+root/static/js/jquery/images/ui-icons_d8e7f3_256x240.png
+root/static/js/jquery/images/ui-icons_e0fdff_256x240.png
+root/static/js/jquery/images/ui-icons_f5e175_256x240.png
+root/static/js/jquery/images/ui-icons_f7a50d_256x240.png
+root/static/js/jquery/images/ui-icons_fcd113_256x240.png
+root/static/js/jquery/index.html
+root/static/js/jquery/jquery-ui.css
+root/static/js/jquery/jquery-ui.js
+root/static/js/jquery/jquery-ui.min.css
+root/static/js/jquery/jquery-ui.min.js
+root/static/js/jquery/jquery-ui.structure.css
+root/static/js/jquery/jquery-ui.structure.min.css
+root/static/js/jquery/jquery-ui.theme.css
+root/static/js/jquery/jquery-ui.theme.min.css
+root/static/js/jquery/jquery.orgchart.css
+root/static/js/jquery/jquery.orgchart.js
+root/static/js/timeline/timeframe.js
+root/static/struct.css
+root/static/style.css
+script/latmos_accounts_web_cgi.pl
+script/latmos_accounts_web_create.pl
+script/latmos_accounts_web_fastcgi.pl
+script/latmos_accounts_web_server.pl
+script/latmos_accounts_web_test.pl
+t/01app.t
+t/02pod.t
+t/03podcoverage.t
+t/04config_load.t
+t/controller_About.t
+t/controller_Ajax.t
+t/controller_Aliases.t
+t/controller_Create.t
+t/controller_Groups.t
+t/controller_Json-DpmtRH.t
+t/controller_Json-Employment.t
+t/controller_Json-EmploymentByPeriod.t
+t/controller_Json-Expire.t
+t/controller_Json-List.t
+t/controller_Json-RefVal.t
+t/controller_Json-Stat.t
+t/controller_Json-Timeline.t
+t/controller_Json.t
+t/controller_Logout.t
+t/controller_Logs.t
+t/controller_Remote-AskPwReset.t
+t/controller_Remote-Listing.t
+t/controller_Remote-Obj.t
+t/controller_Remote-PwReset.t
+t/controller_Remote-Search.t
+t/controller_Remote.t
+t/controller_Search-Adv.t
+t/controller_Search.t
+t/controller_Sites.t
+t/controller_Summary-Employment.t
+t/controller_Summary-EmploymentByPeriod.t
+t/controller_Summary-Offices.t
+t/controller_Users-Employment.t
+t/controller_Users.t
+t/model_Accounts.t
+t/model_AttrForms.t
+t/view_Json.t
+t/view_TT.t
+t/view_TTpart.t
Index: tags/5.2.23/LATMOS-Accounts-Web/MANIFEST.SKIP
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/MANIFEST.SKIP	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/MANIFEST.SKIP	(revision 2119)
@@ -0,0 +1,4 @@
+inc/.*
+.*\.rpm
+.*\.tar[^.]*$
+.*~$
Index: tags/5.2.23/LATMOS-Accounts-Web/Makefile.PL
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/Makefile.PL	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/Makefile.PL	(revision 2119)
@@ -0,0 +1,106 @@
+use inc::Module::Install;
+
+name 'LATMOS-Accounts-Web';
+all_from 'lib/LATMOS/Accounts/Web.pm';
+
+#requires 'Catalyst' => '5.7015';
+requires 'Catalyst::Plugin::ConfigLoader';
+requires 'Catalyst::Plugin::Static::Simple';
+requires 'Catalyst::Action::RenderView';
+requires 'YAML'; # This should reflect the config file format you've chosen
+                 # See Catalyst::Plugin::ConfigLoader for supported formats
+requires 'Catalyst::Plugin::Authentication' => '0';
+requires 'Catalyst::Plugin::Session' => '0';
+requires 'Catalyst::Plugin::Session::Store::File' => '0';
+requires 'Catalyst::Plugin::Session::State::Cookie' => '0';
+requires 'Catalyst::Plugin::Prototype' => '0';
+requires 'Catalyst::Plugin::I18N' => '0';
+requires 'Catalyst::View::TT' => '0';
+requires 'LATMOS::Accounts' => '0';
+requires 'Email::Send';
+requires 'Email::MIME::Creator';
+catalyst;
+
+install_script glob('script/*.pl');
+#auto_install;
+WriteMakefile(
+    macro => { 
+        DESTRPMDIR => '$(shell pwd)',
+    },
+);
+
+package MY;
+
+sub postamble {
+    my @po = map { m:lib/LATMOS/Accounts/Web/I18N/([^\.]+)\.po$:; $1 }
+            glob('lib/LATMOS/Accounts/Web/I18N/*.po');
+
+    my $makepo = join("\n\n", map {
+        "
+lib/LATMOS/Accounts/Web/I18N/$_.po: lib/LATMOS/Accounts/Web/I18N/messages.pot
+\t[ -f lib/LATMOS/Accounts/Web/I18N/$_.po ] || msginit --no-translator --input=lib/LATMOS/Accounts/Web/I18N/messages.pot --output= --locale=$_
+\tmsgmerge -U lib/LATMOS/Accounts/Web/I18N/$_.po lib/LATMOS/Accounts/Web/I18N/messages.pot
+
+        "
+        } @po
+    );
+
+    $makepo .= "\n\npo: " . join(' ', map { "lib/LATMOS/Accounts/Web/I18N/$_.po" } @po) . "\n";
+
+    <<EOF;
+.PHONY: lib/LATMOS/Accounts/Web/I18N/messages.pot
+
+pot: lib/LATMOS/Accounts/Web/I18N/messages.pot
+
+lib/LATMOS/Accounts/Web/I18N/messages.pot:
+\tcat MANIFEST | egrep '^(lib|root/html)' | xargs xgettext.pl -o lib/LATMOS/Accounts/Web/I18N/messages.pot
+
+$makepo
+
+svnmanifest:
+\tsvn ls -R| grep -v "/\$\$"  > MANIFEST
+
+ChangeLog:
+\tsvn log > ChangeLog
+
+latmos-accounts-web.spec: latmos-accounts-web.spec.in Makefile
+\tsed -e 's/\@VERSION@/\$(VERSION)/' < \$< > \$@
+
+rpm: \$(DISTVNAME).tar.gz latmos-accounts-web.spec
+\tmkdir \$(DESTRPMDIR)/noarch || :
+\trpmbuild -ba --clean\\
+\t --define "_sourcedir `pwd`" \\
+\t --define "_specdir `pwd`" \\
+\t --define "_srcrpmdir \$(DESTRPMDIR)" \\
+\t --define "_rpmdir \$(DESTRPMDIR)" \\
+\t latmos-accounts-web.spec
+
+svnrpm: \$(DISTVNAME).tar.gz latmos-accounts-web.spec
+\tmkdir \$(DESTRPMDIR)/noarch || :
+\trpmbuild -ba --clean\\
+\t --define "_sourcedir `pwd`" \\
+\t --define "_specdir `pwd`" \\
+\t --define "_srcrpmdir \$(DESTRPMDIR)" \\
+\t --define "_rpmdir \$(DESTRPMDIR)" \\
+\t --define "svnrelease `LC_ALL=C svn info | grep '^Revision:' | sed 's/Revision: //'`" \\
+\t latmos-accounts-web.spec
+
+EOF
+}
+
+sub install {
+    my ($self) = @_;
+    my $section = $self->SUPER::install();
+
+    $section =~ s/(^install ::.*)/$1 install_config/m;
+
+    $section .= q[
+install_config ::
+	install -d $(DESTDIR)/etc
+	install -d $(DESTDIR)/etc/latmos-accounts
+	install etc/latmos-accounts-web.yml $(DESTDIR)/etc/latmos-accounts/latmos-accounts-web.yml
+];
+
+    return $section;
+}
+
Index: tags/5.2.23/LATMOS-Accounts-Web/README
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/README	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/README	(revision 2119)
@@ -0,0 +1,1 @@
+Run script/latmos_accounts_web_server.pl to test the application.
Index: tags/5.2.23/LATMOS-Accounts-Web/apache/proxy.conf
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/apache/proxy.conf	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/apache/proxy.conf	(revision 2119)
@@ -0,0 +1,46 @@
+<IfModule mod_proxy.c>
+	<Proxy *>
+	Order deny,allow
+	Allow from all
+	</Proxy>
+
+	# ReverseProxy
+	ProxyRequests Off
+	ProxyPass /phpldapadmin !
+	ProxyPass / http://localhost:4000/
+	ProxyPassReverse / http://localhost:4000/
+
+	RequestHeader set REMOTE_USER %{REMOTE_USER}e env=REMOTE_USER
+	RequestHeader set SSL_CLIENT_S_DN %{SSL_CLIENT_S_DN}s
+	RequestHeader set SSL_CLIENT_S_DN_Email %{SSL_CLIENT_S_DN_Email}s
+</IfModule>
+
+<IfModule mod_cache.c>
+	CacheDisable /
+</IfModule>
+
+<Location />
+Satisfy              any
+
+SSLVerifyClient      optional
+SSLVerifyDepth       2
+SSLOptions           +FakeBasicAuth +StdEnvVars
+#SSLOptions           +FakeBasicAuth +StrictRequire +StdEnvVars
+#SSLRequire           %{SSL_CLIENT_S_DN_OU} == "UMR8190" and %{SSL_CLIENT_S_DN_O} == "CNRS"
+
+#AuthType             basic
+#AuthName             "Protected Intranet Area"
+#AuthzLDAPMethod ldap
+#AuthzLDAPServer "ldap.aero.jussieu.fr"
+#AuthzLDAPMapBase dc=latmos,dc=ipsl,dc=fr
+#AuthzLDAPUserScope subtree
+#AuthzLDAPUserBase dc=latmos,dc=ipsl,dc=fr
+#AuthzLDAPUserKey uid
+#AuthzLDAPRoleAttributeName uid
+#AuthzLDAPMapUserToAttr      uid
+#AuthzLDAPSetGroupAuth       map
+#AuthzLDAPMemberKey          memberUid
+#Require valid-user
+
+Allow from all
+</Location>
Index: tags/5.2.23/LATMOS-Accounts-Web/etc/httpd/latmos-accounts-web.conf
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/etc/httpd/latmos-accounts-web.conf	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/etc/httpd/latmos-accounts-web.conf	(revision 2119)
@@ -0,0 +1,15 @@
+<IfModule mod_proxy.c>
+	<Proxy *>
+	Order deny,allow
+	Allow from all
+	</Proxy>
+
+	# ReverseProxy
+	ProxyRequests Off
+	ProxyPass / http://localhost:4000/
+	ProxyPassReverse / http://localhost:4000/
+</IfModule>
+
+<IfModule mod_cache.c>
+	CacheDisable /
+</IfModule>
Index: tags/5.2.23/LATMOS-Accounts-Web/etc/init.d/latmos-accounts-web
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/etc/init.d/latmos-accounts-web	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/etc/init.d/latmos-accounts-web	(revision 2119)
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# Latmos-Account web application
+#
+# chkconfig: 345 97 3
+# description: Latmos-Account web application
+
+. /etc/rc.d/init.d/functions
+[ -r /etc/sysconfig/i18n ] && . /etc/sysconfig/i18n
+export LC_ALL
+export LANG
+
+USER=nobody
+PORT=4000
+[ -f /etc/sysconfig/latmos-accounts ] && \
+    . /etc/sysconfig/latmos-accounts
+
+RETVAL=0
+prog=latmos_accounts_web
+
+function start () {
+    gprintf "Starting %s: " "$prog"
+    daemon --user $USER /usr/bin/latmos_accounts_web_server.pl --daemon --fork --port $PORT --host localhost --forcehttps 2>/dev/null
+    [ $? -eq 0 ] && success || failure
+    touch /var/lock/subsys/$prog
+    echo
+}
+
+function stop () {
+    gprintf "Stopping %s: " "$prog"
+    killproc latmos_accounts_web_server.pl
+    rm -f /var/lock/subsys/$prog
+    echo
+}
+
+case "$1" in
+    start)
+	start
+	;;
+    stop)
+	stop
+	;;
+    status)
+	status latmos_accounts_web_server.pl
+	;;
+    restart)
+	stop
+	start
+	;;
+    reload)
+	stop
+	start
+	;;
+    *)
+	gprintf "Usage: $0 {start|stop|status|restart|reload}\n"
+	exit 1
+esac
+
+exit $RETVAL
Index: tags/5.2.23/LATMOS-Accounts-Web/etc/latmos-accounts-web.yml
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/etc/latmos-accounts-web.yml	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/etc/latmos-accounts-web.yml	(revision 2119)
@@ -0,0 +1,47 @@
+---
+# $Id$
+
+# Where to find configuration file:
+# config: /etc/latmos-accounts/latmos-accounts.ini
+
+# The company name to show
+# company: COMPANY NAME
+
+
+# If using SSL authentication:
+ssl:
+    # The environement variable to use to identify a user
+    SSL_ID: HTTP_SSL_CLIENT_S_DN_EMAIL
+    # the attribute to match to SSL_ID
+    ATTRIBUTE_ID: mail
+    
+    # A list of env. variable to test:
+    # var is env. variable
+    # value the value to find
+    # op: the test, default is equality
+    #     possible value are = (equality), re (regular expression)
+    filters:
+#      - var: HTTP_SSL_CLIENT_I_DN
+#        value: ...
+#        op: =
+#      - ...
+
+# The attribute label used in application
+attrs:
+  uidNumber:
+    label: UID
+#    hint: ...
+
+features:
+   # A tools to send mail to selected people:
+#  mailing: 0
+   # The full list of people by office
+#  offices_list: 0
+   # The full list of people by department
+#  departments_list: 0
+   # Address forms for users
+#  user_addresses: 1
+   # Email form for users
+#  user_email: 1
+   # User "my" page
+#  user_my: 1
Index: tags/5.2.23/LATMOS-Accounts-Web/latmos-accounts-web.spec.in
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/latmos-accounts-web.spec.in	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/latmos-accounts-web.spec.in	(revision 2119)
@@ -0,0 +1,112 @@
+%define realname   LATMOS-Accounts-Web
+%define name       latmos-accounts-web
+%define version    @VERSION@
+%define release    %mkrel %{?svnrelease:0.%{svnrelease}}%{?!svnrelease:1}
+
+Name:       %{name}
+Version:    %{version}
+Release:    %{release}
+License:    GPL or Artistic
+Group:      Development/Perl
+Summary:    Latmos Account Web interface
+Source:     %{realname}-%{version}.tar.gz
+Url:        http://search.cpan.org/dist/%{realname}
+BuildRoot:  %{_tmppath}/%{name}-%{version}-%{release}-buildroot
+BuildArch:  noarch
+
+BuildRequires: perl(Catalyst)
+BuildRequires: perl(Catalyst::Plugin::ConfigLoader)
+BuildRequires: perl(Catalyst::Plugin::Static::Simple)
+BuildRequires: perl(Catalyst::Action::RenderView)
+BuildRequires: perl(YAML::XS)
+BuildRequires: perl(Catalyst::Plugin::Authentication)
+BuildRequires: perl(Catalyst::Plugin::Session)
+BuildRequires: perl(Catalyst::Plugin::Session::Store::File)
+BuildRequires: perl(Catalyst::Plugin::Session::State::Cookie)
+BuildRequires: perl(Catalyst::Plugin::Prototype)
+BuildRequires: perl(Catalyst::View::TT)
+BuildRequires: perl(LATMOS::Accounts)
+BuildRequires: perl(Class::Data::Inheritable)
+BuildRequires: perl(Email::Send)
+BuildRequires: perl(Email::MIME::Creator)
+BuildRequires: perl(Catalyst::Plugin::XMLRPC)
+BuildRequires: perl(Mail::Sendmail)
+BuildRequires: perl(Catalyst::View::JSON)
+BuildRequires: perl-Locale-Maketext-Lexicon
+
+Requires: perl(Catalyst)
+Requires: perl(Catalyst::Plugin::ConfigLoader)
+Requires: perl(Catalyst::Plugin::Static::Simple)
+Requires: perl(Catalyst::Action::RenderView)
+Requires: perl(YAML::XS)
+Requires: perl(Catalyst::Plugin::Authentication)
+Requires: perl(Catalyst::Plugin::Session)
+Requires: perl(Catalyst::Plugin::Session::Store::File)
+Requires: perl(Catalyst::Plugin::Session::State::Cookie)
+Requires: perl(Catalyst::Plugin::Prototype)
+Requires: perl(Catalyst::Plugin::I18N)
+Requires: perl(Catalyst::View::TT)
+Requires: perl(LATMOS::Accounts)
+Requires: perl(Class::Data::Inheritable)
+Requires: perl(Catalyst::Plugin::XMLRPC)
+# Requires: apache-mod_fastcgi
+# Requires: perl(FCGI)
+Requires: perl(Mail::Sendmail)
+Requires: apache-mod_proxy
+Requires: perl(Catalyst::View::JSON)
+Requires: perl(JSON::Any)
+Requires: perl(JSON)
+Requires: perl(JSON::XS)
+Requires: perl(Locale::Maketext::Lexicon)
+
+%description
+The Web interface over LATMOS-Account system.
+
+%prep
+%setup -q -n %{realname}-%{version} 
+
+%build
+%{__perl} Makefile.PL INSTALLDIRS=vendor
+%make
+
+%install
+rm -rf %buildroot
+%makeinstall_std
+
+mkdir -p %buildroot/%_var/www/{fcgi-bin/,cgi-bin/}
+
+install -m 755 script/latmos_accounts_web_fastcgi.pl %buildroot/%_var/www/fcgi-bin/
+install -m 755 script/latmos_accounts_web_cgi.pl %buildroot/%_var/www/cgi-bin/
+
+mkdir -p %buildroot/%_sysconfdir/httpd/conf/webapps.d/
+install -m 640 etc/httpd/latmos-accounts-web.conf %buildroot/%_sysconfdir/httpd/conf/webapps.d/
+
+mkdir -p %buildroot/%_sysconfdir/init.d
+install -m 750 etc/init.d/latmos-accounts-web %buildroot/%_sysconfdir/init.d/latmos-accounts-web
+
+%clean
+rm -rf %buildroot
+
+%preun
+%_preun_service %name
+
+%post
+%_post_service %name
+if [ "$1" -ge "1" ] ; then
+    /sbin/service %name restart
+fi
+
+%triggerpostun -- latmos-accounts
+/sbin/service %name restart
+
+%files
+%defattr(-,root,root)
+%doc Changes README
+%_bindir/*
+%{_mandir}/man3/*
+%{_mandir}/man1/*
+%perl_vendorlib/*
+%_var/www/*/*.pl
+%config(noreplace) %_sysconfdir/httpd/conf/webapps.d/*
+%_sysconfdir/init.d/latmos-accounts-web
+%config(noreplace) %_sysconfdir/latmos-accounts/*
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/Catalyst/Authentication/Credential/La.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/Catalyst/Authentication/Credential/La.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/Catalyst/Authentication/Credential/La.pm	(revision 2119)
@@ -0,0 +1,149 @@
+package Catalyst::Authentication::Credential::La;
+
+use strict;
+use warnings;
+use MIME::Base64;
+
+sub new {
+    my ($class) = @_;
+    return bless({}, $class);
+}
+
+sub _search_user {
+    my ($self, $c) = @_;
+    
+    my $attribute = $c->config->{ssl}->{ATTRIBUTE_ID} || 'mail';
+    my $sslid     = $c->config->{ssl}->{SSL_ID}       || 'HTTP_SSL_CLIENT_S_DN_EMAIL';
+    my $value     = $ENV{$sslid} or do {
+        $c->log->debug("Cannot find user: ENV{$sslid} is empty");
+        return;
+    };
+
+    my $base = $c->model('Accounts')->db;
+    # quick prefilter
+    my @res = $base->search_objects(
+        'user',
+        sprintf('%s~%s', $attribute, $value),
+        'active=1',
+    );
+
+    @res = grep {
+        my $o = $base->get_object('user', $_);
+        $o && grep { lc($_) eq lc($value) } $o->get_attributes($attribute);    
+    } @res;
+
+    if (@res == 1) {
+        return $res[0];
+    } else {
+        $c->log->error(sprintf
+            "Can't identify `%s' user from `%s' attributes, multiple user found: %s",
+            $value,
+            $attribute,
+            join(', ', @res),
+        );
+        return;
+    }
+}
+
+sub _ssl_filter {
+    my ($self, $c, $realm, $authinfo) = @_;
+
+    foreach my $f (@{ $c->config->{ssl}->{filters} }) {
+        my $matchtype = $f->{op} || '=';
+        my $var = $ENV{$f->{var}};
+        if ($matchtype eq '=') {
+            do {
+                $c->log->debug(sprintf
+                    'Filter failed: %s = %s',
+                    $var, $f->{value}
+                );
+                return;
+            } unless($var eq $f->{value});
+        }
+        elsif ($matchtype eq 're') {
+            my $value = $f->{value};
+            do {
+                $c->log->debug(sprintf
+                    'Filter failed: %s match %s',
+                    $var, $f->{value}
+                );
+                return;
+            } unless($var =~ m/$value/);
+        }
+        else {
+            $c->log->error(sprintf 'Wrong filter op: %s', $matchtype);
+            return;
+        }
+    }
+
+    return 1;
+}
+
+sub _ssl_auth {
+    my ($self, $c, $realm, $authinfo) = @_;
+
+    if ($c->config->{ssl}->{filters}) {
+        if (!$self->_ssl_filter($c, $realm, $authinfo)) {
+            return;
+        }
+    }
+
+    if (my $u = $self->_search_user($c)) {
+        $c->log->info(sprintf 'SSL auth for %s', $u);
+        $authinfo->{username} = $u;
+        return 1;
+    } else {
+        return;
+    }
+}
+
+sub _login_auth {
+    my ($self, $c, $realm, $authinfo) = @_;
+    if (!$authinfo) {
+        my $authheader = $c->req->headers->header('Authorization')
+            or do {
+            $c->log->debug('No Authorization header found');   
+            return;
+        };
+        # TODO check auth type
+        my ($type, $base64) = $authheader =~ /(\w+) (\S+)/;
+        ($authinfo->{username}, $authinfo->{password})
+            = decode_base64($base64) =~ /^([^:]+):(.*)/;
+    }
+    
+    if($c->model('Accounts')->db->connect(
+        $authinfo->{username},
+        $authinfo->{password}
+    )) {
+        $c->log->info(sprintf 'basic auth for %s', $authinfo->{username});
+        return $realm->find_user($authinfo)
+    } else {
+        return;
+        $c->log->error(sprintf
+            'Invalid password or user for user %s',
+            $authinfo->{username} ||
+            '(none)'
+        );
+    }
+}
+
+sub authenticate {
+    my ($self, $c, $realm, $authinfo) = @_;
+
+    if ($authinfo->{ssl}) {
+        $self->_ssl_auth  ($c, $realm, $authinfo) or do {
+            $c->session_expire_key( __user => 7 * 24 * 3600 );
+            return;
+        };
+    } else {
+        $self->_login_auth($c, $realm, $authinfo) or do {
+            $c->session_expire_key( __user => 8 * 3600 );
+            return;
+        }
+    }
+
+    $c->model('Accounts')->db->{_user} = $authinfo->{username};
+    return $realm->find_user($authinfo);
+}
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web.pm	(revision 2119)
@@ -0,0 +1,153 @@
+package LATMOS::Accounts::Web;
+
+use strict;
+use warnings;
+no utf8;
+
+use Catalyst::Runtime '5.70';
+
+# Set flags and add plugins for the application
+#
+#         -Debug: activates the debug mode for very useful log messages
+#   ConfigLoader: will load the configuration from a YAML file in the
+#                 application's home directory
+# Static::Simple: will serve static files from the application's root 
+#                 directory
+
+use Catalyst qw/
+    ConfigLoader
+    Static::Simple
+    Session
+    Session::State::Cookie
+    Session::Store::File
+    Authentication
+    Prototype
+    XMLRPC
+    I18N
+    /;
+
+our $VERSION = '5.2.23';
+
+# Configure the application. 
+
+# Default value for active/inactive features
+__PACKAGE__->config(
+    features => {
+        # /services/mailing, mass mailing
+        mailing => 0,
+        # /summary/offices
+        offices_list => 0,
+        # /summary/department
+        departments_list => 0,
+        # users/address forms
+        user_addresses => 1,
+        # users/email form
+        user_email => 1,
+        # users/my form
+        user_my => 1,
+        # user status managed by employment
+        user_employment => 0,
+    }
+);
+
+__PACKAGE__->config(
+    objects => {
+        user => {
+            quick_search => [ qw(cn sn givenName description comment uidNumber) ],
+            list => [ qw(sn givenName mail) ],
+        },
+        group => {
+            quick_search => [ qw(cn description comment gidNumber) ],
+            list => [ qw(description) ],
+        },
+        nethost => {
+            quick_search => [ qw(cn description ip cname otherName macaddr serialNumber inventoryNumber) ],
+            list => [ qw(description owner) ],
+        },
+        netzone => {
+            quick_search => [ qw(cn description ip) ],
+            list => [ qw(type description) ],
+        },
+        site => {
+            quick_search => [ qw(cn l) ],
+            list => [ qw(l) ],
+        },
+        aliases => {
+            quick_search => [ qw(name) ],
+            list => [ qw() ],
+        },
+    },
+);
+
+my $config_file = '/etc/latmos-accounts/latmos-accounts-web.yml';
+if (-r $config_file ) {
+    __PACKAGE__->config( 'Plugin::ConfigLoader' => { 
+            file => $config_file 
+        } 
+    );
+}
+
+# http://stackoverflow.com/questions/1664816/is-there-a-way-to-force-c-urifor-in-catalyst-to-generate-a-uri-that-begins-wit
+__PACKAGE__->config( using_frontend_proxy => 1 );
+
+__PACKAGE__->config( name => 'LATMOS::Accounts::Web' );
+
+__PACKAGE__->config->{'Plugin::Authentication'} = {
+    default_realm => 'la',
+    realms => {
+        la => {
+            credential => {
+                class => 'La'
+            },
+            store => {
+                class => 'Null',
+            },
+        },
+    },
+};
+
+__PACKAGE__->config->{'Plugin::I18N'} = {
+    'maketext_options' => {
+        Decode => 0
+    }
+};
+
+
+# 1 week for session
+__PACKAGE__->config->{'Plugin::Session'} = {
+    expires => 3600 * 24 * 30
+};
+
+
+# Start the application
+__PACKAGE__->setup;
+
+
+=head1 NAME
+
+LATMOS::Accounts::Web - Catalyst based application
+
+=head1 SYNOPSIS
+
+    script/latmos_accounts_web_server.pl
+
+=head1 DESCRIPTION
+
+[enter your description here]
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Web::Controller::Root>, L<Catalyst>
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/About.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/About.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/About.pm	(revision 2119)
@@ -0,0 +1,88 @@
+package LATMOS::Accounts::Web::Controller::About;
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+
+use POSIX;
+use DateTime;
+use Date::Calc;
+use Date::Parse;
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::About - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{page}{title} = "A propos...";
+
+    if ($c->user) {
+        my $ouser = $c->model('Accounts')->db->get_object(
+            'user',
+            $c->user->{username},
+        );
+
+        my @cd = split('-', DateTime->from_epoch(
+            epoch => str2time(
+                $ouser->get_attributes('create'),
+            )
+        )->ymd('-'));
+        my @now = split('-', DateTime->now()->ymd('-'));
+
+        my ($Dy,$Dm,$Dd) = Date::Calc::N_Delta_YMD(@cd, @now);
+        $c->stash->{accountExists} = join(', ',
+            ($Dy ? $c->loc('[_1] years', $Dy)  : ()),
+            ($Dm ? $c->loc('[_1] months', $Dm) : ()),
+            ($Dd ? $c->loc('[_1] days', $Dd)   : ()),
+        );
+
+        if (my $expire = $ouser->get_attributes('expire')) {
+            my @ed = split('-', DateTime->from_epoch(
+                epoch => str2time(
+                    $expire
+                )
+            )->ymd('-'));
+
+            my ($Dy,$Dm,$Dd) = Date::Calc::N_Delta_YMD(@now, @ed);
+            $c->stash->{accountExpire} = join(', ',
+                ($Dy ? $c->loc('[_1] years', $Dy)  : ()),
+                ($Dm ? $c->loc('[_1] months', $Dm) : ()),
+                ($Dd ? $c->loc('[_1] days', $Dd)   : ()),
+            );
+        }
+    }
+}
+
+sub echo : XMLRPC {
+    my ( $self, $c, @args ) = @_;
+    return RPC::XML::fault->new( 400, "No input!"
+    ) unless @args;
+    return join ' ', @args;
+}
+
+=head1 AUTHOR
+
+Olivier Thauvin
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Ajax.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Ajax.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Ajax.pm	(revision 2119)
@@ -0,0 +1,216 @@
+package LATMOS::Accounts::Web::Controller::Ajax;
+
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+use LATMOS::Accounts::Utils;
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Ajax - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index 
+
+=cut
+
+sub index : Private {
+    my ( $self, $c ) = @_;
+
+    $c->response->body('Matched LATMOS::Accounts::Web::Controller::Ajax in Ajax.');
+}
+
+sub rawattr : Local {
+    my ($self, $c, $otype) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    if (!$c->req->param('id')) {
+        $c->stash->{value} = '';
+        return;
+    }
+
+    my $obj = $base->get_object($otype, $c->req->param('id')) or do {
+        $c->stash->{value} = '';
+        return;
+    };
+    $c->stash->{value} = $obj->get_c_field($c->req->param('attr')) || '';
+}
+
+sub displayattr : Local {
+    my ($self, $c, $otype, $attr) = @_;
+    $attr ||= $c->req->param('attr');
+    my $attribute = $c->model('Accounts')->db->attribute($otype, $attr) or return;
+    $c->stash->{value} = $attribute->display($c->req->param('id'));
+}
+
+=head2 objexist
+
+Return HTML picture telling if object name is free or already used
+
+=cut
+
+sub objexist : Local {
+    my ($self, $c, $otype) = @_;
+    my $base = $c->model('Accounts')->db;
+    my $val = lc($c->req->param('val') || '');
+    my $obj = $base->get_object($otype, $val);
+
+    $c->stash->{'exists'} = join(', ', $obj->get_attributes($c->req->param('attr') ||
+            'displayName') || $obj->id) if($obj);
+}
+
+sub check_new_name : Local {
+    my ($self, $c, $otype) = @_;
+    my $base = $c->model('Accounts')->db;
+    my $val = $c->req->param('val');
+
+    warn $otype;
+    if ($c->stash->{validity} = (lc($otype) eq 'user' || lc($otype) eq 'group')
+        ? LATMOS::Accounts::Utils::check_ug_validity($val)
+        : LATMOS::Accounts::Utils::check_oid_validity($val)) {
+        warn $c->stash->{validity};
+        return;
+    }
+
+    if (my $obj = $base->get_object($otype, $val)) {
+        $c->stash->{validity} = "$val exists " .
+            ($obj->get_attributes('displayName') || '')
+    }
+}
+
+sub objattrexist : Local {
+    my ($self, $c, $otype, $attr) = @_;
+    my $base = $c->model('Accounts')->db;
+    my $attribute = $base->attribute($otype, $attr) or return;
+    my $val = $attribute->input($c->req->param('val'));
+    my @obj = $val ? $base->search_objects($otype, "$attr=$val") : ();
+
+    if (my $filter = $c->req->param('exclude')) {
+        @obj = grep { $_ ne $filter } @obj;
+    }
+    $c->stash->{'exists'} = join(', ', @obj);
+}
+
+sub cracklib : Local {
+    my ( $self, $c, $login ) = @_;
+    my $base = $c->model('Accounts')->db;
+    my ($password)  = $c->req->param('passwd');
+    my ($cpassword) = $c->req->param('cpasswd');
+    if (my $obj = $base->get_object('user', $login)) {
+        if ($password) {
+            if ((my $crl = $obj->check_password($password)) ne 'ok') {
+                $c->stash->{cracklib} = 'Mauvais mot de passe: ' . $crl;
+            } elsif ($cpassword) {
+                if ($password ne $cpassword) {
+                    $c->stash->{cracklib} = "Mot de passe diffÃ©rents";
+                } else {
+                    $c->stash->{cracklib} = 'ok';
+                }
+            } else {
+                $c->stash->{cracklib} = 'Confirmez le mot de passe';
+            }
+        }
+    }
+}
+
+sub random_password : Local {
+    my ($self, $c) = @_;
+
+    $c->stash->{random_password} =
+    LATMOS::Accounts::Utils::genpassword(
+        %{ $c->req->params() },
+        checkpassword => sub {
+            $self->model('Accounts')->base->check_password(@_)
+        },
+    );
+}
+
+sub check_ug_validity : Local {
+    my ( $self, $c ) = @_;
+    my $val = $c->req->param('val');
+    $c->stash->{validity} = LATMOS::Accounts::Utils::check_ug_validity($val);
+}
+
+sub select_freeip_select : Local {
+    my ($self, $c) = @_;
+
+}
+
+sub user_to_group_form : Local {
+    my ($self, $c, $user) = @_;
+    
+    my $base = $c->model('Accounts')->db;
+
+    $c->stash->{user} = $base->get_object('user', $user) or do {
+        return;
+    };
+
+    my %ingroups =
+        map { $_ => 1 } 
+        @{ $c->stash->{user}->get_c_field('memberOf') || []};
+
+   $c->stash->{othergroups} = [ grep { ! $ingroups{$_} }
+       $base->search_objects('group', 'cn~' . ($c->req->param('gfilter') || '*')) ]; 
+}
+
+sub goto : Local {
+    my ($self, $c) = @_;
+
+    my $objurl = $c->req->param('otype');
+    $objurl .= 's' unless ($objurl =~ /s$/);
+
+    $c->res->redirect(
+        $c->uri_for('/' . $objurl, $c->req->param('goto'),
+        $c->req->param('subform'))
+    );
+    $c->stash->{template} = 'no_object.tt';
+}
+
+sub set_attrs : Local {
+    my ($self, $c, $otype, $name) = @_;
+
+    my $base = $c->model('Accounts')->db;
+    my $object = $base->get_object($otype, $name) or do {
+        return;
+    };
+    my $form = $c->model('AttrForms', '', $object);
+    $form->set_attrs([ $c->req->param('attrs') ]);
+
+}
+
+sub list_objects : Local {
+    my ($self, $c, $otype) = @_;
+    $c->stash->{ofilter} = $c->model('AttrFilter', $otype);
+}
+
+sub select_objects_list : Local {
+    my ($self, $c, $otype) = @_;
+    $c->stash->{ofilter} = $c->model('AttrFilter', $otype);
+}
+
+sub end : Private {
+    my ( $self, $c ) = @_;
+    $c->forward($c->view('TTpart'));
+}
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Aliases.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Aliases.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Aliases.pm	(revision 2119)
@@ -0,0 +1,72 @@
+package LATMOS::Accounts::Web::Controller::Aliases;
+
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Aliases - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{ofilter} = $c->model('AttrFilter', 'aliases');
+}
+
+sub default :LocalPath {
+    my ( $self, $c, undef, $alias) = @_;
+
+    my $db = $c->model('Accounts')->db;
+
+    my $oalias = $db->get_object('aliases', $alias);
+    $c->stash->{aliasesname} = $alias;
+    $c->stash->{aliases} = $oalias;
+    $c->stash->{page}{title} = "Gestion des alias";
+
+    $c->stash->{form} = $c->model('AttrForms', 'aliases', $oalias);
+
+    if ($c->req->param('make_active')) {
+        $c->stash->{aliases}->set_c_fields('exported' => 1);
+        $db->commit;
+    }
+    elsif ($c->req->param('make_inactive')) {
+        $c->stash->{aliases}->set_c_fields('exported' => 0);
+        $db->commit;
+    }
+    elsif ($c->req->param('delete')) {
+        $db->delete_object('aliases', $alias);
+        $db->commit;
+        $c->res->redirect('/aliases');
+        return;
+    }
+    else {
+        $c->stash->{form}->set_attrs;
+    }
+}
+
+=head1 AUTHOR
+
+Olivier Thauvin
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Create.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Create.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Create.pm	(revision 2119)
@@ -0,0 +1,95 @@
+package LATMOS::Accounts::Web::Controller::Create;
+
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+use LATMOS::Accounts::Utils;
+use LATMOS::Accounts::Log;
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Create - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index 
+
+=cut
+
+sub index : Private {
+    my ( $self, $c ) = @_;
+
+    $c->response->body('Matched LATMOS::Accounts::Web::Controller::Create in Create.');
+}
+
+sub default : LocalPath {
+    my ($self, $c, undef, $otype ) = @_;
+    my $base = $c->model('Accounts')->db;
+    my ($step) = $c->req->param('step');
+    my $form = $c->model('AttrForms', undef, $otype, $base);
+    my ($status, $info);
+    $c->stash->{page}{title} = "Ajouter un objet / $otype";
+    if ($step) {
+        $info = $c->session->{ochelper};
+    }
+    if ($info->{name}{ask}) {
+        $info->{name}{content} = $c->req->param('_name');
+    }
+    foreach my $attr (@{$info->{ask} || []}) {
+        my $field = $form->field($attr);
+        my @vals = grep { $_ } $c->req->param($field->attr_field_name());
+        $info->{contents}{$attr} = @vals > 1 ? \@vals : $vals[0]
+            if (@vals);
+    }
+    my $ochelper = $base->ochelper($otype);
+    ($status, $info) = $ochelper->step($info);
+    
+    if ($status eq 'CREATED') {
+        $base->commit;
+        $c->res->redirect($c->uri_for('/',
+                $c->forward('/obj_to_uri', [ $otype ]),
+                $info->{name}{content}));
+    } elsif ($status eq 'ERROR') {
+        # On error, restart the process
+        $c->stash->{page}{error} =
+            'Erreur lors de la crÃ©ation de l\'objet: ' .
+            LATMOS::Accounts::Log::lastmessage(LA_ERR);
+        $base->rollback;
+        foreach my $attr (@{$info->{ask} || []}) {
+            my $field = $form->field($attr);
+            $c->req->params->{$field->attr_field_name()} = $info->{contents}{$attr};
+        }
+        $info->{step} = 0; # reset
+        ($status, $info) = $ochelper->step($info);
+    } else {
+        foreach my $attr (@{$info->{ask} || []}) {
+            my $field = $form->field($attr);
+            $c->req->params->{$field->attr_field_name()} = $info->{contents}{$attr};
+        }
+        $base->rollback;
+    }
+    $c->stash->{form} = $form;
+    $c->stash->{ochelper} = $info; 
+    $c->session->{ochelper} = $info;
+}
+
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Groups.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Groups.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Groups.pm	(revision 2119)
@@ -0,0 +1,114 @@
+package LATMOS::Accounts::Web::Controller::Groups;
+
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Groups - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index 
+
+=cut
+
+sub index : Private {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{ofilter} = $c->model('AttrFilter', 'group');
+    $c->stash->{page}{title} = "Liste des groupes";
+
+}
+
+sub default : LocalPath {
+    my ( $self, $c, undef, $groupname, $subform ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    $c->stash->{page}{title} = "Groupe :: $groupname";
+    $c->stash->{groupname} = $groupname;
+    $c->stash->{group} = $base->get_object('group', $groupname) or do {
+        $c->forward('/no_object');
+        return;
+    };
+
+    if ($c->req->param('make_active')) {
+        $c->stash->{group}->set_c_fields('exported' => 1);
+        $base->commit;
+    }
+    if ($c->req->param('make_inactive')) {
+        $c->stash->{group}->set_c_fields('exported' => 0);
+        $base->commit;
+    }
+    if ($c->req->param('delete')) {
+        $base->delete_object('group', $groupname);
+        $base->commit;
+        $c->res->redirect('/groups');
+    }
+
+    $c->stash->{subform} = $subform || '';
+
+    for ($subform || '') {
+        # Exceptions...
+        /^users$/ and do {
+            $c->stash->{template} = 'groups/users.tt';
+
+            if ($c->req->param('adduser')) {
+                $c->stash->{group}->addAttributeValue('memberUID', $c->req->param('adduser'));
+                $c->stash->{group}->base->commit;
+            } elsif ($c->req->param('deluser')) {
+                $c->stash->{group}->delAttributeValue('memberUID', $c->req->param('deluser'));
+                $c->stash->{group}->base->commit;
+            }
+
+            last;
+        };
+        /^dpmt$/ and do {
+            $c->stash->{template} = 'groups/dpmt.tt';
+            my @filter = ();
+            push(@filter, 'active=1') if (!$c->req->param('inactive'));
+            push(@filter, 'mail=*') if ($c->req->param('mail'));
+            push(@filter, 'company=' . $c->req->param('company')) if
+                ($c->req->param('company'));
+            $c->stash->{dpmt} = $groupname;
+            $c->stash->{odpmt} = $c->stash->{group};
+            $c->stash->{user} = [ $base->search_objects('user',
+                    'department=' . $c->stash->{dpmt},
+                    @filter,
+                ) ];
+            foreach my $u ($base->search_objects('user',
+                    'memberOf=' . $c->stash->{dpmt},
+                    @filter,
+                )) {
+                push(@{$c->{stash}->{guser}}, $u)
+                unless(grep { $u eq $_ } @{$c->stash->{user}});
+            }
+            last;
+        };
+
+        $c->stash->{form} = $c->model('AttrForms', 'group' . $c->stash->{subform}, $c->stash->{group});
+        $c->stash->{form}->set_attrs;
+    }
+}
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json.pm	(revision 2119)
@@ -0,0 +1,54 @@
+package LATMOS::Accounts::Web::Controller::Json;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    $c->response->body('Matched LATMOS::Accounts::Web::Controller::Json in Json.');
+}
+
+sub end : Private {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{NoSync} = 1;
+
+    $c->stash->{data} ||= {};
+    $c->forward($c->view('Json'));
+}
+
+=encoding utf8
+
+=head1 AUTHOR
+
+olivier
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/AccAge.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/AccAge.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/AccAge.pm	(revision 2119)
@@ -0,0 +1,96 @@
+package LATMOS::Accounts::Web::Controller::Json::AccAge;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json::Expire - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    my $graph = {
+        data => [],
+        series => [],
+    };
+
+    my $sortattr = $c->req->param('sort') || 'status';;
+    my $inactive = $c->req->param('inactive');
+
+    my %exists = map { $_ => 1 } $base->search_objects('user', 'active=1');
+    my %create = $base->attributes_summary_by_object('user', 'create');
+    my %sort = $base->attributes_summary_by_object('user', $sortattr);
+
+    my %sortl;
+
+    foreach (keys %sort) {
+        if (!$c->req->param('inactive')) {
+            $exists{$_} or next;
+        }
+        my $s = $sort{$_}[0];
+        $sortl{$s} = 1;
+    }
+    $sort{'(none)'} = 1;
+    
+    my %dates;
+    foreach my $user (keys %create) {
+        if (!$c->req->param('inactive')) {
+            $exists{$user} or next;
+        }
+        foreach my $date (@{ $create{$user} }) {
+            $date or next;
+            $date =~ s/-.*//;
+            my $status = $sort{$user}[0];
+
+            $status ||= '(none)';
+
+            $dates{$_}{$date} ||= 0 foreach(keys %sortl);
+            $dates{$status}{$date} ++;
+
+            $sortl{$status} = 1;
+        }
+    }
+
+    foreach my $status (sort keys %sortl) {
+        push(@{ $graph->{data} }, [ map { [ $_, $dates{$status}{$_} +0 ] } sort { $a cmp $b } keys %{ $dates{$status} } ]);
+        push(@{ $graph->{series} }, { label => $status });
+    } 
+    
+    $c->stash->{data} = $graph;
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/DpmtRH.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/DpmtRH.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/DpmtRH.pm	(revision 2119)
@@ -0,0 +1,62 @@
+package LATMOS::Accounts::Web::Controller::Json::DpmtRH;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json::DpmtRH - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub Fetch :Path :Args(1) {
+    my ( $self, $c, $dpmt ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    my %exists = map { $_ => 1 } $base->search_objects('user', 'active=1', "department=$dpmt");
+    my %status = $base->attributes_summary_by_object('user', 'contratType');
+
+    my %dpmtstat;
+
+    foreach my $user (keys %status) {
+        $exists{$user} or next;
+        foreach (@{ $status{$user} }) {
+            $dpmtstat{$_} ||= 0;
+            $dpmtstat{$_}++;
+        }
+    }
+
+    $c->stash->{data} = [ map { [ $_ . " ($dpmtstat{$_})", $dpmtstat{$_} +0 ] } sort { $dpmtstat{$b} <=> $dpmtstat{$a} } keys %dpmtstat ];
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Employment.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Employment.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Employment.pm	(revision 2119)
@@ -0,0 +1,172 @@
+package LATMOS::Accounts::Web::Controller::Json::Employment;
+use Moose;
+use namespace::autoclean;
+
+use DateTime;
+use Date::Parse;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json::Employment - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    my %employments;
+    my $now = DateTime->now->ymd;
+    my $past = $c->req->param('past');
+
+    my $key = $c->req->param('key') || 'department';
+    my @filter = $c->req->param('filter') || '';
+
+    my $keyobj = $key =~ m/^(company|department|contratType|manager)$/
+        ? 'employment'
+        : 'user';
+
+    my $filterobj = $filter[0] =~ m/^(company|department|contratType|manager)=/
+        ? 'employment'
+        : 'user';
+
+    @filter = grep { $_ } @filter;
+    my %end = $base->attributes_summary_by_object('employment', 'lastday');
+    my %start = $base->attributes_summary_by_object('employment', 'firstday');
+    my %empusers = $base->attributes_summary_by_object('employment', 'user');
+    my %keys = $base->attributes_summary_by_object($keyobj, $key);
+
+    my %filteredObj = map { $_ => 1 } $base->search_objects($filterobj, @filter);
+
+    my %keysentry;
+    foreach my $emp (keys %start) {
+        if (!$past) {
+            # We filter pasted employment
+            next if ($end{$emp}[0] && $end{$emp}[0] lt $now);
+        }
+
+        if ($filterobj eq 'user') {
+            $filteredObj{ $empusers{$emp}[0] } or next;
+        } else {
+            $filteredObj{ $emp } or next;
+        }
+ 
+        my $keyvalues = $keyobj eq 'user'
+            ? $keys{ $empusers{$emp}[0] }
+            : $keys {$emp};
+
+        $employments{ $emp } = {
+           start => $start{$emp}[0],
+           end   => $end  {$emp}[0],
+           key   => $keyvalues,
+       };
+       $keysentry{ $keyvalues->[0] || $c->loc('(none)') } = [];
+    }
+
+    my %done;
+
+    my @dates;
+   
+    foreach (map { $employments{$_}->{start}  }
+             grep { $employments{$_}->{start} } (keys %employments)) {
+             if (!$past) {
+                $_ gt $now or next;
+             }
+             my $dt = DateTime->from_epoch( epoch => str2time($_));
+             $dt->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+             $dt->subtract(days => 1);
+             push(@dates, $_, $dt->ymd('-'));
+    }
+    foreach (map { $employments{$_}->{end}  }
+             grep { $employments{$_}->{end} } (keys %employments)) {
+             if (!$past) {
+                $_ gt $now or next;
+             }
+             my $dt = DateTime->from_epoch( epoch => str2time($_));
+             $dt->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+             $dt->add(days => 1);
+             push(@dates, $_, $dt->ymd('-'));
+    }
+
+    @dates = sort(@dates);
+
+    if (@dates) {
+        my $dt = DateTime->from_epoch( epoch => str2time($dates[-1]));
+        $dt->add(days => 60);
+        push(@dates, $dt->ymd);
+    }
+
+    if (!@dates) {
+        foreach (1 .. 12) {
+            push(@dates, DateTime->now->add(months => $_)->ymd);
+        }
+    }
+
+    push(@dates, $now);
+
+    foreach my $emp (sort { $a cmp $b } @dates) {
+        $done{$emp} and next;
+        $done{$emp} = 1;
+
+        my %sum = map { $_ => 0 } keys %keysentry;
+        $sum{ $c->loc('(none)') } = 0;
+
+        foreach my $cemp (keys %employments) {
+            #warn "$cemp $emp" . $employments{$cemp}->{start} . " " . $employments{$cemp}->{end} ;
+            if ($employments{$cemp}->{start} le $emp && ((!$employments{$cemp}->{end}) || $employments{$cemp}->{end} ge $emp)) {
+                foreach my $k (@{ $employments{$cemp}->{key} || [ $c->loc('(none)') ] }) {
+                    $sum{$k} ++;
+                }
+            }
+        }
+
+        foreach (keys %keysentry) {
+           push (@{ $keysentry{$_} }, [ $emp, $sum{$_}+0 ]);
+       } 
+    }
+
+    my $data;
+    foreach my $k (sort { $keysentry{$a}->[0][1] <=> $keysentry{$b}->[0][1] } keys %keysentry) {
+        push(@{ $data->{data} }, $keysentry{$k});
+        push(@{ $data->{series} }, { label => $k || $c->loc('(none)') });
+    }
+
+    $c->stash->{data} = $data;
+
+}
+
+
+
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/EmploymentByPeriod.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/EmploymentByPeriod.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/EmploymentByPeriod.pm	(revision 2119)
@@ -0,0 +1,85 @@
+package LATMOS::Accounts::Web::Controller::Json::EmploymentByPeriod;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json::EmploymentByPeriod - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    my ($sday, $smonth, $syear) = split('/', $c->req->param('start'));
+    my ($eday, $emonth, $eyear) = split('/', $c->req->param('end'));
+
+    my $start = DateTime->new(year => $syear, month => $smonth, day => $sday);
+    my $end   = DateTime->new(year => $eyear, month => $emonth, day => $eday);
+
+    my @filteredObj = $base->search_objects(
+        'employment', sprintf('firstday<=%s', $end->ymd('-')), sprintf('lastday>=%s', $start->ymd('-')), 'lastday=NULL'
+    );
+    my %atstart = map { $_ => 1 } $base->search_objects(
+        'employment', sprintf('firstday>=%s', $start->ymd('-'))
+    );
+    my %atend = map { $_ => 1 } $base->search_objects(
+        'employment', sprintf('lastday<=%s', $end->ymd('-'))
+    );
+
+    my %empusers = $base->attributes_summary_by_object('employment', 'user');
+    my %emptypes = $base->attributes_summary_by_object('employment', 'contratType');
+
+    my %results;
+    my $atstart = $c->req->param('atstart');
+    my $atend =   $c->req->param('atend');
+
+    foreach my $emp (@filteredObj) {
+        if ($atstart) {
+            $atstart{$emp} or next;
+        }
+        if ($atend) {
+            $atend{$emp} or next;
+        }
+        my $user = $empusers{$emp}[0];
+        my $type = $emptypes{$emp}[0] || '(N/A)';
+        $results{$type}{$user} = 1;
+    }
+
+    $c->stash->{data} = [ 
+        sort { $b->[1] <=> $a->[1] } 
+        map { [ $_, scalar(keys %{$results{$_}}) ] } keys %results
+    ];
+}
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Expire.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Expire.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Expire.pm	(revision 2119)
@@ -0,0 +1,80 @@
+package LATMOS::Accounts::Web::Controller::Json::Expire;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+use DateTime;
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json::Expire - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    my $graph = {
+        data => [],
+        series => [],
+    };
+
+    my @exists = $base->search_objects('user', 'active=1', 'givenName=*');
+    my %results = $base->attributes_summary_by_object('user', 'expire');
+    my %contrats = $base->attributes_summary_by_object('user', 'contratType');
+    
+    my %dates;
+    my $today = DateTime->now()->ymd() . ' 00:00';
+    foreach my $user (@exists) {
+        if (my $d = $results{$user}) {
+            my $contrat = $contrats{$user}[0] || $c->loc('(none)');
+            $dates{$contrat}{$today} ||= 0;
+            foreach (@{ $results{$user} }) {
+                $_ or next;
+                $_ =~ s/:\d\d\+\d\d$//;
+                $dates{$contrat}{$_} ||= 0;
+                $dates{$contrat}{$_} ++;
+            }
+        }
+    }
+
+    foreach my $contrat (sort keys %dates) {
+        push(@{ $graph->{data} }, [ map { [ $_, $dates{$contrat}{$_} +0 ] } sort { $dates{$contrat}{$b} <=> $dates{$contrat}{$a} } keys %{ $dates{$contrat} } ]);
+        push(@{ $graph->{series} }, { label => $contrat });
+    } 
+    
+    $c->stash->{data} = $graph;
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/FreeIp.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/FreeIp.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/FreeIp.pm	(revision 2119)
@@ -0,0 +1,57 @@
+package LATMOS::Accounts::Web::Controller::Json::FreeIp;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json::RefVal - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+    my $zone = $base->get_object('netzone', $c->req->param('zone'))
+        or return;
+
+    my $term = $c->req->param('term') || '';
+    $c->stash->{data} = [
+        { label => 'IP alÃ©atoire', value => 'any'   }, 
+        { label => 'PremiÃšre IP',  value => 'first' },
+        map { { label => $_, value => $_ } } grep { $_ =~ /\Q$term\E/i } $zone->get_attributes('freeIP') 
+    ];
+
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+olivier
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Info.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Info.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Info.pm	(revision 2119)
@@ -0,0 +1,63 @@
+package LATMOS::Accounts::Web::Controller::Json::Info;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json::Info - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path {
+    my ( $self, $c, $otype) = @_;
+
+    my $base = $c->model('Accounts')->db;
+    $otype ||= $c->req->param('otype') or return;
+
+    my $name = $c->req->param('name') or return;
+
+    my $object = $base->get_object($otype, $name) or return;
+
+    my @infos;
+
+    foreach my $attr (sort { $a cmp $b } $base->list_canonical_fields($otype, 'r')) {
+        $base->check_acl($object, $attr, 'r') or next;
+        my $val = $object->get_c_field($attr);
+        my @vals = ref $val ? @{ $val } : $val;
+        foreach (@vals) {
+            push(@infos, { n => $attr, v => $_ });
+        }
+    }
+
+    $c->stash->{data} = \@infos;
+}
+
+=encoding utf8
+
+=head1 AUTHOR
+
+olivier
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/List.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/List.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/List.pm	(revision 2119)
@@ -0,0 +1,57 @@
+package LATMOS::Accounts::Web::Controller::Json::List;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json::List - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+    my $otype = $c->req->param('otype') or return;
+
+    my @objects = $base->list_objects($otype);
+    if (my $term = $c->req->param('term')) {
+        @objects = grep { /\Q$term\E/ } @objects;
+    }
+
+    $c->stash->{data} = \@objects;
+    #[ map { { label => $_ } } @objects ];
+
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+olivier
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/RefVal.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/RefVal.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/RefVal.pm	(revision 2119)
@@ -0,0 +1,75 @@
+package LATMOS::Accounts::Web::Controller::Json::RefVal;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json::RefVal - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(2) {
+    my ( $self, $c, $otype, $attr ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    my $attribute = $base->attribute($otype, $attr);
+    my $term = $c->req->param('term') || '';
+
+    my @values = $attribute->can_values;
+
+    if (my $oref = $attribute->reference) {
+        if ($term) {
+            my %objs = ();
+            foreach my $attr (@{ $c->config->{objects}{$oref}{quick_search} || [ 'name' ]}) {
+                foreach ($base->search_objects(
+                        $oref, "$attr~" . $term, 'exported=true')) {
+                    $objs{$_} = 1;
+                }
+            }
+            @values = grep { $objs{$_} } @values;
+        }
+
+        if (scalar(@values) <= 20) {
+            $c->stash->{data} = [
+                map { my $o = $base->get_object($attribute->reference, $_); { label => "$o", value => $_ } }
+                @values ];
+        } else {
+            $c->stash->{data} = [ map { { label => $_, value => $_ } } @values ];
+        }
+    } else {
+        $c->stash->{data} = [ map { { label => $_, value => $_ } } grep { $_ =~ /\Q$term\E/i } @values ];
+    }
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+olivier
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Stat.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Stat.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Stat.pm	(revision 2119)
@@ -0,0 +1,109 @@
+package LATMOS::Accounts::Web::Controller::Json::Stat;
+use Moose;
+use namespace::autoclean;
+use POSIX;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json::Stat - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(1) {
+    my ( $self, $c, $statname ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    my $ostat = $base->get_object('stat', $statname) or return;
+
+    my $res = $ostat->compute or return;
+
+    $c->stash->{data} = [ map { [ $_, $res->{$_} +0 ] } sort { $res->{$b} <=> $res->{$a} } keys %$res ];
+}
+
+sub history :Local :Args(1) {
+    my ( $self, $c, $statname ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    my $ostat = $base->get_object('stat', $statname) or return;
+
+    my $data = $ostat->getAllStat or return;
+
+    my $res = $ostat->compute or return;
+
+    my $nowdate = POSIX::strftime('%Y-%m-%d %H:%M', localtime);
+    foreach my $val (sort keys %$res) {
+        $data->{$nowdate}{$val} = $res->{$val} + 0;
+    }
+
+    my $graph = {
+        series => [],
+        data => [],
+    };
+
+    my %lines;
+
+    my @dates = sort keys %$data;
+
+    for (my $i = 0; $i <= $#dates; $i++) {
+        my $date = $dates[$i];
+
+        if ($i >= 1) {
+            my $tdate = $dates[$i - 1];
+            foreach my $val (sort keys %{ $data->{$tdate} }) {
+                $data->{$tdate}{$val} or next;
+                $data->{$date}{$val} ||= 0;
+            }
+        }
+        if ($i < $#dates) {
+            my $tdate = $dates[$i + 1];
+            foreach my $val (sort keys %{ $data->{$tdate} }) {
+                $data->{$tdate}{$val} or next;
+                $data->{$date}{$val} ||= 0;
+            }
+        }
+        foreach my $val (sort keys %{ $data->{$date} }) {
+            $res->{ $val } ||= 0;
+            push(@{ $lines{$val} }, [ $date, $data->{$date}{$val} + 0 ]);
+        }
+    }
+
+    foreach (sort { $res->{$b} <=> $res->{$a} } keys %lines) {
+        push(@{ $graph->{data} }, $lines{$_});
+        push(@{ $graph->{series} }, { label => $_ });
+    }
+
+    $c->stash->{data} = $graph;
+}
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm	(revision 2119)
@@ -0,0 +1,174 @@
+package LATMOS::Accounts::Web::Controller::Json::Timeline;
+use Moose;
+use namespace::autoclean;
+
+use DateTime;
+use Date::Parse;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json::Timeline - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(1) {
+    my ( $self, $c, $user ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+    my $ouser = $base->get_object('user', $user)
+        or return;
+
+    my @colors = qw(black silver green blue red);
+    my %colorset;
+
+
+    my $var = {
+        start => $ouser->get_attributes('create'),
+        end => undef,
+        employments => {
+            name => 'Employment',
+            color => '#000000',
+            spans => [],
+            events => [],
+        },
+    };
+
+    foreach my $emp ($ouser->listEmployment) {
+        my $employment = $base->get_object('employment', $emp);
+
+        my $vemp = {
+            name => ($employment->get_attributes('contratType') || ''),
+            start => $employment->get_attributes('firstday'),
+            end => $employment->get_attributes('lastday') || undef,
+        };
+
+        if (!$colorset{$vemp->{name}}) {
+            $colorset{$vemp->{name}} = $colors[0];
+            push(@colors, shift(@colors));
+        }
+        $vemp->{color} =  $colorset{$vemp->{name}};
+
+        if (!$vemp->{end}) {
+            my $dt = DateTime->now();
+            $dt->add(days => 3 * 365);
+            $vemp->{end} = $dt->ymd('-') . ' ' . $dt->hms;
+        }
+
+        $vemp->{start} .= ' 00:00:00';
+        $vemp->{end}   .= ' 23:59:59';
+
+        push(@{$var->{employments}{spans}}, $vemp);
+    }
+
+    my $dtnow = DateTime->now;
+    push(@{$var->{employments}{events}}, {
+            name => $c->loc('Today'),
+            date => $dtnow->ymd . ' ' . $dtnow->hms,
+    });
+    push(@{$var->{employments}{events}}, {
+            name => $c->loc('Created'),
+            date => $ouser->get_attributes('create'),
+    });
+    if (my $expire = $ouser->get_attributes('expire')) {
+        push(@{$var->{employments}{events}}, {
+                name => $c->loc('Expire'),
+                date => $expire,
+                options => {
+                    type => 'alarm',
+                }
+        });
+    }
+    if (my $endcircuit = $ouser->get_attributes('endcircuit')) {
+        push(@{$var->{employments}{events}}, {
+                name => $c->loc('End of entrance'),
+                date => $endcircuit,
+                options => {
+                    type => 'alarm',
+                }
+        });
+    }
+    if (my $date = $ouser->get_attributes('startEmployment')) {
+        push(@{$var->{employments}{events}}, {
+                name => $c->loc('Activity start'),
+                date => $date,
+                options => {
+                    type => 'employment',
+                    showdate => 1,
+                }
+        });
+    } elsif (my $nextemp = $ouser->get_attributes('nextEmployment')) {
+        my $emp = $base->get_object('employment', $nextemp);
+        my $date = $emp->get_attributes('firstday') . ' 00:00:00';
+        push(@{$var->{employments}{events}}, {
+                name => $c->loc('Activity start'),
+                date => $date,
+                options => {
+                    type => 'employment',
+                    showdate => 1,
+                }
+        });
+    }
+
+    if (my $date = $ouser->get_attributes('endEmployment')) {
+        push(@{$var->{employments}{events}}, {
+                name => $c->loc('Activity end'),
+                date => $date,
+                options => {
+                    type => 'employment',
+                    showdate => 1,
+                }
+        });
+    } elsif (my $date2 = $ouser->get_attributes('endLastEmployment')) {
+        push(@{$var->{employments}{events}}, {
+                name => $c->loc('Activity end'),
+                date => $date2,
+                options => {
+                    type => 'employment',
+                    showdate => 1,
+                }
+        });
+    }
+
+    foreach (@{ $var->{employments}{spans} }) {
+        $var->{end}   = $_->{end}   if ((!$var->{end})   || $_->{end} gt $var->{end});
+        $var->{start} = $_->{start} if ((!$var->{start}) || $_->{start} lt $var->{start});
+    }
+    foreach (@{ $var->{employments}{events} }) {
+        $var->{end}   = $_->{date}   if ((!$var->{end})   || $_->{date} gt $var->{end});
+        $var->{start} = $_->{date}   if ((!$var->{start}) || $_->{date} lt $var->{start});
+    }
+
+    $c->stash->{data} = $var;
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Tree.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Tree.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Json/Tree.pm	(revision 2119)
@@ -0,0 +1,89 @@
+package LATMOS::Accounts::Web::Controller::Json::Tree;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Json::Expire - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    my %users = map { $_ => 1 } $base->search_objects('user', 'active=1', 'givenName=*');
+    my %dpmts = map { $_ => 1 } $base->search_objects('group', 'sutype=dpmt');
+    my %dpmtmanager = $base->attributes_summary_by_object('group', 'managedBy');
+    my %usermanager = $base->attributes_summary_by_object('user', 'managerContact');
+    my %dpmtuser    = $base->attributes_summary_by_object('user', 'department');
+
+    my %nodes;
+    my $data = {
+        nodes => [],
+        links => [],
+    };
+
+    foreach my $user (keys %users) {
+        my $ouser = $base->get_object('user', $user);
+        push(@{ $data->{nodes} }, {
+                "name" => $ouser->id,
+                label => $ouser->get_attributes('displayName'),
+        });
+        $nodes{$ouser->id} = $#{ $data->{nodes} };
+    }
+
+    foreach my $user (keys %users) {
+        my $manager = $usermanager{$user}->[0] ||
+            $dpmtmanager { $dpmtuser{ $user }->[0] }->[0] or next;
+
+        if (!$nodes{$manager}) {
+            my $ouser = $base->get_object('user', $manager);
+            $nodes{$manager} = $nodes{$ouser->id};
+        }
+        $nodes{$manager} or next;
+
+        push(@{ $data->{links} },
+            {
+                "source" => $nodes{$user},
+                "target" => $nodes{$manager},
+            }
+        );
+
+    }
+
+    $c->stash->{data} = $data;
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Login.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Login.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Login.pm	(revision 2119)
@@ -0,0 +1,66 @@
+package LATMOS::Accounts::Web::Controller::Login;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Login - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+sub begin : Private {
+    my ( $self, $c ) = @_;
+    $c->forward('/needlogin');
+    return 1;
+}
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{NoSync} = 1;
+
+    if ($c->req->param('username')) {
+        if ($c->authenticate({
+                username => $c->req->param('username') || '',
+                password => $c->req->param('password') || '',
+                ssl      => $c->req->param('use_ssl')  || '',
+        })) {
+            my $redirurl = $c->session->{redirurl} || $c->uri_for('/');
+            $redirurl->query_form(%{ $c->session->{redirparam} || {}});
+            $c->session->{redirurl} = undef;
+            $c->session->{redirparam} = undef;
+            $c->res->redirect($redirurl);
+        } else {
+            # invalid login...
+        }
+    }
+}
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Logout.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Logout.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Logout.pm	(revision 2119)
@@ -0,0 +1,45 @@
+package LATMOS::Accounts::Web::Controller::Logout;
+use Moose;
+use namespace::autoclean;
+
+BEGIN {extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Logout - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+sub begin : Private {
+}
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    $c->logout;
+}
+
+=head1 AUTHOR
+
+olivier
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Logs.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Logs.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Logs.pm	(revision 2119)
@@ -0,0 +1,102 @@
+package LATMOS::Accounts::Web::Controller::Logs;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Logs - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+sub auto : Private {
+    my ( $self, $c ) = @_;
+    my $base = $c->model('Accounts')->db;
+
+    unless ($base->check_acl($c->stash->{user}, 'Log', 'r')) {
+        $c->go('/no_object');
+        return;
+    }
+
+    return 1;
+}
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $modifier = $c->req->param('modifier');
+    my $otype    = $c->req->param('otype');
+    my $object   = $c->req->param('object');
+    my $okey     = $c->req->param('okey');
+
+    my (@where, @values);
+    my $limit = 1000;
+
+    if ($okey) {
+        $c->stash->{logobject} = $c->model('Accounts')->db->getObjectFromOKey($okey);
+        push(@where, 'ikey = ?');
+        push(@values, $okey);
+        if (my $obj = $c->stash->{logobject}) {
+            $c->req->params->{otype} = $obj->type;
+            $c->req->params->{object} = $obj->id;
+        }
+    } elsif ($otype) {
+        push(@where, 'otype = ?');
+        push(@values, $otype);
+
+        if ($object) {
+            push(@where, '"name" = ?');
+            push(@values, $object);
+
+            $c->stash->{logobject} = $c->model('Accounts')->db->get_object($otype, $object);
+            $limit = undef;
+        }
+    }
+
+    if ($modifier) {
+        push(@where, '"username" =  ?');
+        push(@values, $modifier);
+    }
+
+    my $search = $c->model('Accounts')->db->db->prepare_cached(
+        sprintf(
+            q{select * from objectslogs %s order by logdate desc %s},
+            (@where ? 'where ' . join(' and ', @where) : ''),
+            ($limit ? "LIMIT $limit" : ''),
+        )
+    );
+
+    $search->execute(@values);
+
+    $c->stash->{logs} = $search->fetchall_arrayref({});
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/NetHosts.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/NetHosts.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/NetHosts.pm	(revision 2119)
@@ -0,0 +1,100 @@
+package LATMOS::Accounts::Web::Controller::NetHosts;
+
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Users - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index 
+
+=cut
+
+sub index : Private {
+    my ( $self, $c ) = @_;
+    
+    $c->stash->{ofilter} = $c->model('AttrFilter', 'nethost');
+    my $start = $c->req->param('start');
+}
+
+sub default : LocalPath {
+    my ( $self, $c, undef, $nethostname ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+    $c->stash->{page}{title} = "HÃŽte rÃ©seau :: $nethostname";
+    $c->stash->{nethost} = $base->get_object('nethost', $nethostname) or do {
+        $c->forward('/no_object');
+        return;
+    };
+    $c->stash->{nethostname} = $nethostname;
+    $c->stash->{form} = $c->model('AttrForms', 'nethost', $c->stash->{nethost});
+    if ($c->req->param('addip')) {
+        for (1) {
+            my $zone = $base->get_object('netzone', $c->req->param('zone'))
+                or last;
+            my $ip = $c->req->param('newip') || 'any';
+            if ($ip =~ /^(any|first)$/) {
+                my @freeips = $zone->get_attributes('freeIP') or last;
+                my $idx = $ip eq 'first' ? 0 : rand(scalar(@freeips));
+                $ip = $freeips[$idx] or last;
+            }
+            my @currentips = grep { $_ } $c->stash->{nethost}->get_attributes('ip');
+            $c->stash->{nethost}->set_c_fields(ip => [ @currentips, $ip ])
+                or do {
+                $base->rollback;
+                last;
+            };
+            $base->commit;
+            last;
+        }
+    } elsif ($c->req->param('rename') && $c->req->param('to')) {
+        my $to = $c->req->param('to');
+        if ($base->rename_nethost($nethostname, $to,
+            addcname => $c->req->param('addcname') || undef,
+        )) {
+            $base->commit;
+            $c->res->redirect($to);
+        } else {
+            $base->rollback;
+        }
+    } elsif ($c->req->param('make_active')) {
+        $c->stash->{nethost}->set_c_fields('exported' => 1);
+        $base->commit;
+    }
+    elsif ($c->req->param('make_inactive')) {
+        $c->stash->{nethost}->set_c_fields('exported' => 0);
+        $base->commit;
+    }
+    elsif ($c->req->param('delete')) {
+        $base->delete_object('nethost', $nethostname);
+        $base->commit;
+        $c->res->redirect('/nethosts');
+
+    } else {
+        $c->stash->{form}->set_attrs;
+    }
+}
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/NetZones.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/NetZones.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/NetZones.pm	(revision 2119)
@@ -0,0 +1,70 @@
+package LATMOS::Accounts::Web::Controller::NetZones;
+
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Users - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index 
+
+=cut
+
+sub index : Private {
+    my ( $self, $c ) = @_;
+    
+    $c->stash->{ofilter} = $c->model('AttrFilter', 'netzone');
+    $c->stash->{page}{title} = "Liste des zones rÃ©seaux";
+}
+
+sub default : LocalPath {
+    my ( $self, $c, undef, $netzonename ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+    $c->stash->{netzone} = $base->get_object('netzone', $netzonename) or do {
+        $c->forward('/no_object');
+        return;
+    };
+    $c->stash->{page}{title} = "Zone rÃ©seau :: $netzonename";
+    $c->stash->{netzonename} = $netzonename;
+    $c->stash->{form} = $c->model('AttrForms', 'netzone', $c->stash->{netzone});
+    $c->stash->{form}->set_attrs;
+
+    if ($c->req->param('make_active')) {
+        $c->stash->{netzone}->set_c_fields('exported' => 1);
+        $base->commit;
+    }
+    if ($c->req->param('make_inactive')) {
+        $c->stash->{netzone}->set_c_fields('exported' => 0);
+        $base->commit;
+    }
+    if ($c->req->param('delete')) {
+        $base->delete_object('netzone', $netzonename);
+        $base->commit;
+        $c->res->redirect('/netzones');
+    }
+}
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/OAliases.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/OAliases.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/OAliases.pm	(revision 2119)
@@ -0,0 +1,86 @@
+package LATMOS::Accounts::Web::Controller::OAliases;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::OAliases - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+}
+
+sub Create :Path('create') :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $otype = $c->req->param('otype');
+    my $name  = $c->req->param('name');
+
+    if (my $for = $c->req->param('oalias')) {
+        if ($c->model('Accounts')->db->CreateAlias($otype, $name, $for)) {
+            $c->res->redirect($c->uri_for('/oaliases', $otype, $name));
+            $c->model('Accounts')->db->commit;
+        } else {
+            $c->model('Accounts')->db->rollback;
+        }
+    }
+}
+
+sub Edit :Path :Args(2) {
+    my ( $self, $c, $otype, $name ) = @_;
+
+    my $oalias = $c->model('Accounts')->db->GetAlias($otype, $name) or do {
+        $c->go('/no_object');
+        return;
+    };
+
+    if ($c->req->param('delete')) {
+        if ($c->model('Accounts')->db->RemoveAlias($otype, $name)) {
+            $c->res->redirect($c->uri_for('/oaliases'));
+            $c->model('Accounts')->db->commit;
+            return 1;
+        }
+    } elsif (my $for = $c->req->param('oalias')) {
+        my $exported = $c->req->param('exported') || 0;
+        if($oalias->set_c_fields(oalias => $for, exported => $exported)) {
+            $oalias->base->commit;
+
+        }
+    }
+
+    $c->stash->{objectalias} = $oalias;
+
+}
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote.pm	(revision 2119)
@@ -0,0 +1,63 @@
+package LATMOS::Accounts::Web::Controller::Remote;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Remote - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+sub begin : Private { }
+
+sub auto : Private {
+    my ( $self, $c ) = @_;
+
+    if ($c->user_exists) {
+        # Set login user:
+        $c->model('Accounts')->db->{_user} = $c->user->{username};
+    }
+
+    return 1;
+}
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    $c->response->body('Matched LATMOS::Accounts::Web::Controller::Remote in Remote.');
+}
+
+sub end : Private {
+    my ( $self, $c ) = @_;
+    $c->stash->{data} ||= {};
+    $c->forward($c->view('Json'));
+}
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/AskPwReset.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/AskPwReset.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/AskPwReset.pm	(revision 2119)
@@ -0,0 +1,73 @@
+package LATMOS::Accounts::Web::Controller::Remote::AskPwReset;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Remote::AskPwReset - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $url = $c->req->param('url');
+
+    my $username = $c->req->param('user') or do {
+        return;
+    };
+
+    my $user = $c->model('Accounts')->db->get_object('user', $username) or do {
+        $c->stash->{data}{err} = 'No such user';
+        $c->log->debug($c->stash->{data}{err});
+        return;
+    };
+
+    if (!$user->_get_attributes('active')) {
+        $c->stash->{data}{err} = 'No such user';
+        $c->log->debug($c->stash->{data}{err});
+        return;
+    }
+
+    if (!$user->SendPasswordReset($url)) {
+        $c->stash->{data}{err} = 'unknown error';
+        $c->log->debug($c->stash->{data}{err});
+        return;
+    } else {
+        $c->stash->{data}{ok} = 'Done';
+        $user->base->commit;
+        return 
+    }
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/ChangePw.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/ChangePw.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/ChangePw.pm	(revision 2119)
@@ -0,0 +1,81 @@
+package LATMOS::Accounts::Web::Controller::Remote::ChangePw;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Remote::ChangePw - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $username = $c->req->param('user') or do {
+        $c->stash->{data}{err} = 'Invalid request';
+        return;
+    };
+
+    my $user = $c->model('Accounts')->db->get_object('user', $username) or do {
+        $c->stash->{data}{err} = 'No such user';
+        return;
+    };
+
+    if (!$user->_get_attributes('active')) {
+        $c->stash->{data}{err} = 'No such user';
+        return;
+    }
+
+    my $oldpassword = $c->req->param('oldpassword');
+    my $password = $c->req->param('password');
+
+    if (!$c->model('Accounts')->db->authenticate_user(
+            $username, $oldpassword
+        )) {
+        $c->stash->{data}{err} = 'Invalid request';
+        return;
+    }
+
+    my $error = $c->forward('/users/change_password', [ $username, $password ]);
+
+    if ($error) {
+        $c->stash->{data}{err} = $error;
+        return 
+    } else {
+        $user->base->commit;
+        $c->stash->{data}{ok} = 'Done';
+        return 1;
+    }
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/Listing.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/Listing.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/Listing.pm	(revision 2119)
@@ -0,0 +1,74 @@
+package LATMOS::Accounts::Web::Controller::Remote::Listing;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Remote::Listing - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    my $otype = $c->req->param('otype');
+    my @search = $c->req->param('q');
+    my @attrs = $c->req->param('attribute');
+
+    push(@search, 'oalias=NULL') unless($c->req->param('oalias'));
+
+    if (!$base->is_supported_object($otype)) {
+        $c->res->status('404');
+        return;
+    }
+
+    my %objects = map { $_ => {} } $c->model('Accounts')->db->search_objects($otype, @search);
+
+    foreach my $attr (@attrs) {
+        foreach (keys %objects) {
+            $objects{$_}{$attr} = [];
+        }
+        my %res = $base->attributes_summary_by_object($otype, $attr);
+        foreach my $obj (keys %res) {
+            exists($objects{$obj}) or next;
+            $objects{$obj}{$attr} = $res{$obj};
+        }
+    }
+
+    $c->stash->{NoSync} = 1;
+    $c->stash->{data} = \%objects;
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/Obj.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/Obj.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/Obj.pm	(revision 2119)
@@ -0,0 +1,87 @@
+package LATMOS::Accounts::Web::Controller::Remote::Obj;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Remote::Obj - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{data} = [
+        $c->model('Accounts')->db->list_supported_objects
+    ];
+}
+
+sub List :Path :Args(1) {
+    my ($self, $c, $otype) = @_;
+
+    if (!$c->model('Accounts')->db->is_supported_object($otype)) {
+        $c->res->status('404');
+        return;
+    }
+
+    $c->stash->{data} = [
+        $c->req->param('oalias')
+            ? $c->model('Accounts')->db->listRealObjects($otype)
+            : $c->model('Accounts')->db->list_objects($otype)
+    ]
+}
+
+sub Show :Path :Args(2) {
+    my ($self, $c, $otype, $id) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    if (!$base->is_supported_object($otype)) {
+        $c->res->status('404');
+        return;
+    }
+
+    my $obj = $base->get_object($otype, $id);
+
+    if (!$obj) {
+        $c->res->status('404');
+        return;
+    } 
+
+    foreach my $attr (sort { $a cmp $b } $base->list_canonical_fields($otype, 'r')) {
+        my $oattr = $obj->attribute($attr) or next;
+        if ($oattr->hidden) { next; }
+        my $val = $obj->get_c_field($attr);
+        $c->stash->{data}{$attr} = $val if ($val);
+    } 
+}
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/PwReset.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/PwReset.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/PwReset.pm	(revision 2119)
@@ -0,0 +1,80 @@
+package LATMOS::Accounts::Web::Controller::Remote::PwReset;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Remote::AskPwReset - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $username = $c->req->param('user') or do {
+        $c->stash->{data}{err} = 'Invalid request';
+        return;
+    };
+
+    my $user = $c->model('Accounts')->db->get_object('user', $username) or do {
+        $c->stash->{data}{err} = 'No such user';
+        return;
+    };
+
+    if (!$user->_get_attributes('active')) {
+        $c->stash->{data}{err} = 'No such user';
+        return;
+    }
+
+    my $resetid = $c->req->param('id');
+    my $password = $c->req->param('password');
+
+    if (!$user->CheckPasswordResetId($resetid)) {
+        $c->stash->{data}{err} = 'Invalid request';
+        return;
+    }
+
+    my $error = $c->model('Accounts')->ChangeUserPassword( $username, $password );
+
+    if ($error) {
+        $c->stash->{data}{err} = $error;
+        return 
+    } else {
+        $user->DeletePasswordId($resetid);
+        $user->base->commit;
+        $c->stash->{data}{ok} = 'Done';
+        return 1;
+    }
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/Search.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/Search.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/Search.pm	(revision 2119)
@@ -0,0 +1,63 @@
+package LATMOS::Accounts::Web::Controller::Remote::Search;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Remote::Search - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $otype = $c->req->param('otype');
+    my @search = $c->req->param('q');
+    $c->stash->{NoSync} = 1;
+
+    push(@search, 'oalias=NULL') unless($c->req->param('oalias'));
+
+    my $base = $c->model('Accounts')->db;
+
+    if (!$base->is_supported_object($otype)) {
+        $c->res->status('404');
+        return;
+    }
+
+    $c->stash->{data} = [
+        $c->model('Accounts')->db->search_objects($otype, @search)
+    ];    
+
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/Write.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/Write.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Remote/Write.pm	(revision 2119)
@@ -0,0 +1,115 @@
+package LATMOS::Accounts::Web::Controller::Remote::Write;
+use Moose;
+use namespace::autoclean;
+use LATMOS::Accounts::Log;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Remote::Write - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+}
+
+sub create :Local :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $name  = $c->req->param('name');
+    my $otype = $c->req->param('otype');
+
+    my %info = ();
+
+    foreach my $attr (keys %{ $c->req->params }) {
+        $attr =~ /^(name|otype)$/ and next;
+        $info{$attr} = $c->req->params->{$attr};
+    }
+
+    my $res = $c->model('Accounts')->db->create_c_object($otype, $name, %info);
+    if ($res) {
+        $c->model('Accounts')->db->commit;
+    } else {
+        $c->stash->{data}{error} = LATMOS::Accounts::Log::lastmessage(LA_ERR);
+    }
+
+    $c->stash->{data}{result} = $res ? 1 : 0;
+}
+
+sub modify :Local :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $name  = $c->req->param('name');
+    my $otype = $c->req->param('otype');
+
+    my %info = ();
+
+    foreach my $attr (keys %{ $c->req->params }) {
+        $attr =~ /^(name|otype)$/ and next;
+        $info{$attr} = $c->req->params->{$attr};
+    }
+
+    if (my $obj = $c->model('Accounts')->db->get_object($otype, $name)) {
+        $c->stash->{data}{result} = $obj->set_c_fields(%info) || 0;
+        if ($c->stash->{data}{result}) {
+            $c->model('Accounts')->db->commit;
+        } else {
+            $c->stash->{data}{error} = LATMOS::Accounts::Log::lastmessage(LA_ERR);
+        }
+    } else {
+        $c->stash->{data}{result} = 0;
+        $c->stash->{data}{error} = 'No such object';
+    }
+}
+
+sub delete :Local :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $name  = $c->req->param('name');
+    my $otype = $c->req->param('otype');
+    if (!$c->model('Accounts')->db->get_object($otype, $name)) {
+        $c->stash->{data}{result} = 0;
+        $c->stash->{data}{error} = 'No such object';
+        return;
+    }
+
+    my $res = $c->model('Accounts')->db->delete_object($otype, $name); 
+    $c->stash->{data}{result} = $res ? 1 : 0;
+    if ($c->stash->{data}{result}) {
+        $c->model('Accounts')->db->commit;
+    } else {
+        $c->stash->{data}{error} = LATMOS::Accounts::Log::lastmessage(LA_ERR);
+    }
+}
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Root.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Root.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Root.pm	(revision 2119)
@@ -0,0 +1,170 @@
+package LATMOS::Accounts::Web::Controller::Root;
+
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+no utf8;
+use POSIX;
+use DateTime;
+use Date::Calc;
+use Date::Parse;
+
+#
+# Sets the actions in this controller to be registered with no prefix
+# so they function identically to actions created in MyApp.pm
+#
+__PACKAGE__->config->{namespace} = '';
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Root - Root Controller for LATMOS::Accounts::Web
+
+=head1 DESCRIPTION
+
+[enter your description here]
+
+=head1 METHODS
+
+=cut
+
+sub component_disabled : Private {
+    my ( $self, $c ) = @_;
+    $c->res->status(403);
+}
+
+sub auto : Private {
+    my ( $self, $c ) = @_;
+
+    my ($locale) = split(',', $c->req->header('Accept-Language') || '');
+    $locale ||= '';
+    $locale =~ s/(\s\s)/$1/;
+    if (my $plocale = ($c->req->param('locale')|| $c->session->{'locale'})) {
+        $c->session->{'locale'} = $locale = $plocale;
+    }
+    POSIX::setlocale(POSIX::LC_ALL, $locale);
+    $c->languages($locale ? [ $locale ] : '');
+}
+
+sub begin : Private {
+    my ( $self, $c ) = @_;
+
+    if ($c->user_exists) {
+        # Set login user:
+        $c->model('Accounts')->db->{_user} = $c->user->{username};
+        return 1;
+    } else {
+        # No need to login for About section
+        $c->detach('needlogin');
+        return;
+    }
+}
+
+=head2 default
+
+=cut
+
+sub default : Private {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{Nosync} = 1;
+
+    $c->res->status(404); 
+}
+
+sub index : Private {
+    my ( $self, $c ) = @_;
+
+    if ($c->user) {
+        $c->res->redirect(
+            $c->uri_for('/users', $c->user->{username})
+        )
+    }
+}
+
+sub needlogin : Private {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{Nosync} = 1;
+
+    my $path = $c->req->path || '';
+
+    if ($path !~ /^(login|static)/) {
+        $c->session->{redirurl} = $c->req->uri;
+        $c->session->{redirparam} = $c->req->params;
+    }
+
+    my $sslenv = $c->config->{ssl}->{SSL_ID} || 'HTTP_SSL_CLIENT_S_DN_EMAIL';
+    my $sslid = $ENV{$sslenv};
+    for ($sslid) {
+        $_ or last;
+
+        /^\(?null\)?$/i and last;
+
+        my @id = $c->model('Accounts')->db->search_objects('user', "mail~$sslid");
+
+        scalar(@id) == 1 or last;
+
+        $c->stash->{sslid} = $sslid;
+    }
+
+    $c->stash->{template} = 'login/index.tt';
+    return 0;
+}
+
+sub no_object : Local {
+    my ($self, $c) = @_;
+    $c->stash->{Nosync} = 1;
+    $c->stash->{template} = 'no_object.tt';
+}
+
+sub obj_to_uri : Private {
+    my ($self, $c, $ref) = @_;
+    my $uri_part = {
+        user => 'users',
+        group => 'groups',
+        nethost => 'nethosts',
+        netzone => 'netzones',
+        site => 'sites',
+        aliases => 'aliases',
+        service => 'services',
+    }->{$ref} || $ref;
+}
+
+sub obj_to_label : Private {
+    my ($self, $c, $ref) = @_;
+    my $uri_part = {
+        user => 'Utilisateurs',
+        group => 'Groupes',
+        nethost => 'HÃŽtes rÃ©seaux',
+        netzone => 'Zones rÃ©seau',
+        site => 'Sites',
+        aliases => 'Alias',
+        service => 'Services',
+    }->{$ref} || $ref;
+}
+
+=head2 end
+
+Attempt to render a view, if needed.
+
+=cut
+
+sub end : ActionClass('RenderView') {
+    my ($self, $c) = @_;
+    $c->forward($c->view('TT')) unless($c->res->body);
+    $c->model('Accounts')->db->rollback;
+    $c->model('Accounts')->call_batch_sync unless($c->stash->{NoSync});
+}
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Rpc.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Rpc.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Rpc.pm	(revision 2119)
@@ -0,0 +1,73 @@
+package LATMOS::Accounts::Web::Controller::Rpc;
+use Moose;
+use namespace::autoclean;
+$RPC::XML::ENCODING = 'utf-8';
+
+BEGIN {extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Rpc - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub _begin : Private {
+    my ($self, $c) = @_;
+}
+
+sub index : Public {
+    my ($self, $c) = @_;
+    $c->xmlrpc;
+}
+
+sub ech : XMLRPC('ech.ech') {
+    my ( $self, $c, @args ) = @_;
+    return RPC::XML::fault->new( 400, "No input!" ) unless
+    @args;
+    return join ' ', @args;
+}
+sub user_change_password : XMLRPC('user.change_password') {
+    my ( $self, $c, $username, $oldpasswd, $newpasswd ) = @_;
+    return RPC::XML::fault->new( 400, "No input!" ) unless $newpasswd;
+
+    $c->authenticate({
+        username => $username,
+        password => $oldpasswd,
+    }) or return RPC::XML::fault->new( 400, "Cannot authenticate" );
+
+    my $msg = $c->forward('/users/change_password', [ $username, $newpasswd ]);
+    if ($msg) {
+        return RPC::XML::fault->new( 400, "Error: $msg" );
+    } else {
+        return "Password changed";
+    }
+}
+
+
+sub end : Private {}
+
+=head1 AUTHOR
+
+Olivier Thauvin
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Search.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Search.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Search.pm	(revision 2119)
@@ -0,0 +1,43 @@
+package LATMOS::Accounts::Web::Controller::Search;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Search - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+}
+
+
+=head1 AUTHOR
+
+olivier
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Search/Adv.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Search/Adv.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Search/Adv.pm	(revision 2119)
@@ -0,0 +1,69 @@
+package LATMOS::Accounts::Web::Controller::Search::Adv;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Search::Adv - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{db} = $c->model('Accounts')->db;
+
+    if ($c->req->param('otype')) {
+        $c->session->{searchOtype} = $c->req->param('otype');
+    } elsif ($c->req->param('attr')) {
+        push(@{$c->session->{searchValues}}, sprintf('%s~%s', $c->req->param('attr'), $c->req->param('attrval')));
+    } elsif ($c->req->param('delval')) {
+        my @delvals = $c->req->param('delval');
+        my @newvals = ();
+        foreach my $v (@{$c->session->{searchValues} || []}) {
+            grep { $_ eq $v } @delvals and next;
+            push(@newvals, $v);
+        }
+        $c->session->{searchValues} = \@newvals;
+    } elsif(grep { $_ && $_ eq 'new' } $c->req->query_keywords) {
+        $c->session->{searchOtype} = undef;
+        $c->session->{searchValues} = [];
+    }
+    
+    if ($c->session->{searchOtype}) {
+        $c->stash->{objs} = [ $c->stash->{db}->search_objects(
+            $c->session->{searchOtype},
+            @{ $c->session->{searchValues} || []},
+        ) ];
+    }
+
+}
+
+
+=head1 AUTHOR
+
+olivier
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Services.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Services.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Services.pm	(revision 2119)
@@ -0,0 +1,56 @@
+package LATMOS::Accounts::Web::Controller::Services;
+
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Users - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index 
+
+=cut
+
+sub index : Private {
+    my ( $self, $c ) = @_;
+    
+    $c->stash->{ofilter} = $c->model('AttrFilter', 'service');
+    my $start = $c->req->param('start');
+}
+
+sub default : LocalPath {
+    my ( $self, $c, undef, $servicename ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+    $c->stash->{page}{title} = "Service :: $servicename";
+    $c->stash->{service} = $base->get_object('service', $servicename) or do {
+        $c->forward('/no_object');
+        return;
+    };
+    $c->stash->{servicename} = $servicename;
+    $c->stash->{form} = $c->model('AttrForms', 'services', $c->stash->{service});
+    $c->stash->{form}->set_attrs;
+}
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Sites.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Sites.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Sites.pm	(revision 2119)
@@ -0,0 +1,79 @@
+package LATMOS::Accounts::Web::Controller::Sites;
+
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Site - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index 
+
+=cut
+
+sub index : Private {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{ofilter} = $c->model('AttrFilter', 'site');
+    $c->stash->{page}{title} = "Liste des sites";
+}
+
+sub default : LocalPath {
+    my ( $self, $c, undef, $sitename, $subform ) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    $c->stash->{page}{title} = "Site :: $sitename";
+    $c->stash->{sitename} = $sitename;
+    $c->stash->{site} = $base->get_object('site', $sitename) or do {
+        $c->forward('/no_object');
+        return;
+    };
+
+    if ($c->req->param('make_active')) {
+        $c->stash->{site}->set_c_fields('exported' => 1);
+        $base->commit;
+    }
+    if ($c->req->param('make_inactive')) {
+        $c->stash->{site}->set_c_fields('exported' => 0);
+        $base->commit;
+    }
+    if ($c->req->param('delete')) {
+        $base->delete_object('site', $sitename);
+        $base->commit;
+        $c->res->redirect('/sites');
+    }
+
+    $c->stash->{subform} = $subform || '';
+
+    for ($subform) {
+        # Exceptions...
+        # /^$/ $c->stash->{template} =
+
+        $c->stash->{form} = $c->model('AttrForms', 'site' . $c->stash->{subform}, $c->stash->{site});
+        $c->stash->{form}->set_attrs;
+    }
+}
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Stat.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Stat.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Stat.pm	(revision 2119)
@@ -0,0 +1,57 @@
+package LATMOS::Accounts::Web::Controller::Stat;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Summary::Rh - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub auto : Private {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{NoSync} = 1;
+}
+
+sub index :Path :Args(0) {
+    my ( $self, $c, $statname ) = @_;
+
+}
+
+sub display :Path :Args(1) {
+    my ( $self, $c, $statname ) = @_;
+
+    $c->stash->{statname} = $statname;
+}
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+olivier
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/AccAge.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/AccAge.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/AccAge.pm	(revision 2119)
@@ -0,0 +1,55 @@
+package LATMOS::Accounts::Web::Controller::Summary::AccAge;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Expire - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{jsonurl} ||= $c->uri_for('/json/accage', $c->req->params);
+}
+
+sub contrat :Path('contrat')  :Args(0) {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{jsonurl} = $c->uri_for('/json/accagecontrat');
+
+    $c->go('index');
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Departments.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Departments.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Departments.pm	(revision 2119)
@@ -0,0 +1,42 @@
+package LATMOS::Accounts::Web::Controller::Summary::Departments;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Summary::Department - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+}
+
+
+=head1 AUTHOR
+
+olivier
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Employment.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Employment.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Employment.pm	(revision 2119)
@@ -0,0 +1,46 @@
+package LATMOS::Accounts::Web::Controller::Summary::Employment;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Summary::Employment - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/EmploymentByPeriod.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/EmploymentByPeriod.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/EmploymentByPeriod.pm	(revision 2119)
@@ -0,0 +1,50 @@
+package LATMOS::Accounts::Web::Controller::Summary::EmploymentByPeriod;
+use Moose;
+use namespace::autoclean;
+use DateTime;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Summary::EmploymentByPeriod - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $year = DateTime->now->year;
+    $c->req->params->{start} ||= "01/01/$year";
+    $c->req->params->{end}   ||= "31/12/$year";
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Expire.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Expire.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Expire.pm	(revision 2119)
@@ -0,0 +1,62 @@
+package LATMOS::Accounts::Web::Controller::Summary::Expire;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Expire - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+        my $base = $c->model('Accounts')->db;
+
+        my $users = $base->fetchObjectInfo(
+            'user',
+            [ qw(sn givenName description expire contratType) ],
+            'active=1', 'expire=*',
+        );
+
+
+        my %list = ();
+        foreach (keys %$users) {
+            my $ex = $users->{$_}->{expire}->[0] or next;
+            push(@{ $list{$ex} }, $users->{$_});
+        }
+
+        $c->stash->{expirelist} = \%list;
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Offices.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Offices.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Offices.pm	(revision 2119)
@@ -0,0 +1,91 @@
+package LATMOS::Accounts::Web::Controller::Summary::Offices;
+use Moose;
+use namespace::autoclean;
+
+BEGIN {extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Summary::Offices - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $base = $c->model('Accounts')->base;
+
+    my $users = $base->fetchObjectInfo(
+        'user',
+        [ qw(sn givenName description) ],
+        'active=1'
+    );
+
+    my $sites = $base->fetchObjectInfo(
+        'site',
+        [ qw(l description) ],
+        'exported=true'
+    );
+
+    my $addresses = $base->fetchObjectInfo(
+        'address',
+        [ qw(user site physicalDeliveryOfficeName telephoneNumber l isMainAddress) ],
+        'exported=true'
+    );
+
+    foreach my $ad (keys %$addresses) {
+        my $sitename = $addresses->{$ad}{site}[0] || $c->loc('(other)');
+
+        my $loc = $addresses->{$ad}{site}[0]
+            ? $sites->{ $addresses->{$ad}{site}[0] }{l}[0]
+            : $addresses->{$ad}{l}[0];
+
+        my $office = $addresses->{$ad}{physicalDeliveryOfficeName}[0] || '';
+
+        my $user = $users->{ $addresses->{$ad}{user}[0] } or next;
+
+        $c->stash->{sites} = $sites;
+
+        push (
+            @{$c->stash->{office}{$sitename}{$office}},
+            {
+                displayName => ($user->{sn}[0]
+                    ? sprintf('%s %s', $user->{givenName}[0], $user->{sn}[0])
+                    : $user->{description}[0]),
+                phone => $addresses->{$ad}{telephoneNumber}[0],
+                isMainAddress => $addresses->{$ad}{isMainAddress}[0],
+                id => $ad,
+                user => $addresses->{$ad}{user}[0],
+                l => $loc,
+                site => $sitename,
+            }
+        );
+    }
+}
+
+
+=head1 AUTHOR
+
+olivier
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Tree.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Tree.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Summary/Tree.pm	(revision 2119)
@@ -0,0 +1,46 @@
+package LATMOS::Accounts::Web::Controller::Summary::Tree;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Expire - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Tools/CSV.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Tools/CSV.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Tools/CSV.pm	(revision 2119)
@@ -0,0 +1,109 @@
+package LATMOS::Accounts::Web::Controller::Tools::CSV;
+use Moose;
+use namespace::autoclean;
+use LATMOS::Accounts::Utils;
+use LATMOS::Accounts::Log;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Tools::MassCreation - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    if ($c->req->upload('csv')) {
+        $c->go('parseCSV');
+    }
+}
+
+sub parseCSV : Private {
+    my ( $self, $c ) = @_;
+
+    my $upload = $c->req->upload('csv');
+    my $otype = $c->req->param('otype');
+    $c->stash->{otype} = $otype;
+
+    my $error = 0;
+    loadCSV(
+        $upload->fh,
+        cb => sub {
+            my ($res, $count) = @_;
+
+            my $ochelper = $c->model('Accounts')->db->ochelper($otype);
+
+            my $info = {
+                contents => $res
+            };
+
+            if ($res->{name}) {
+                $info->{name}{content} = $res->{name};
+            }
+
+            if ($ochelper->Automate($info)) {
+                push(@{ $c->stash->{results} },
+                    {
+                        objectName => $info->{name}{content},
+                        info => $res,
+                    }
+                );
+                push(@{ $c->session->{selectedObjects} }, $info->{name}{content});
+                $c->session->{selectedOtype} = $otype;
+            } else {
+                $error = 1;
+                push(@{ $c->stash->{failures} },
+                    {
+                        line => $count,
+                        error => LATMOS::Accounts::Log::lastmessage,
+                    }
+                );
+            }
+            return 1;
+        },
+        initcb => sub {
+            my ($csv) = @_;
+            @{ $c->stash->{attrs} } = $csv->column_names;
+        },
+    );
+
+    if ($error) {
+        $c->stash->{failure} = 1;
+        $c->model('Accounts')->db->rollback;
+    } else {
+        $c->model('Accounts')->db->commit;
+    }
+    
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Tools/LoadNew.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Tools/LoadNew.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Tools/LoadNew.pm	(revision 2119)
@@ -0,0 +1,71 @@
+package LATMOS::Accounts::Web::Controller::Tools::LoadNew;
+use Moose;
+use namespace::autoclean;
+
+use LATMOS::Accounts::Utils;
+use File::Temp;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Tools::LoadNew - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    if (my $upload = $c->req->upload('input')) {
+        my %attrs = LATMOS::Accounts::Utils::parse_obj_file($upload->fh);
+        my $otype = $c->req->param('otype');
+        $c->session->{ochelper} = {
+            contents => \%attrs,
+        };
+        $c->req->params->{step} = '1';
+        $c->go('/create/default', [ undef, $otype ]);
+    } elsif (my $content = $c->req->param('content')) {
+        my $tmp = File::Temp->new();
+        print $tmp $content;
+        $tmp->seek( 0, 0 );
+
+        my %attrs = LATMOS::Accounts::Utils::parse_obj_file($tmp);
+        $tmp = undef;
+        my $otype = $c->req->param('otype');
+        $c->session->{ochelper} = {
+            contents => \%attrs,
+        };
+        $c->req->params->{step} = '1';
+        $c->go('/create/default', [ undef, $otype ]);
+    }
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Tools/LoadPasswd.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Tools/LoadPasswd.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Tools/LoadPasswd.pm	(revision 2119)
@@ -0,0 +1,82 @@
+package LATMOS::Accounts::Web::Controller::Tools::LoadPasswd;
+use Moose;
+use namespace::autoclean;
+
+use LATMOS::Accounts::Log;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Tools::LoadPasswd - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+    my ( $self, $c ) = @_;
+
+    my $account = $c->model('Accounts');
+
+    if (my $upload = $c->req->upload('input')) {
+
+        my $crypt = $c->req->param('crypt');
+        my $force = $c->req->param('force');
+
+        my $fh =  $upload->fh;
+        while (my $line = <$fh>) {
+            my ($username, $password) = split(/:/, $line);
+            $username or next;
+            $password or next;
+
+            if ($crypt) {
+                my $user = $account->db->get_objects('user', $username)
+                    or do {
+                    push(@{ $c->stash->{Pass} }, { u => $username, r => $c->localize('No such user') });
+                    next;
+                };
+                $account->db->check_acl($user, 'userPassword', 'w')
+                    or do {
+                    push(@{ $c->stash->{Pass} }, { u => $username, r => $c->localize('Permission denied') });
+                    next;
+                };
+
+                if ($user->InjectCryptPasswd($password)) {
+                    push(@{ $c->stash->{Pass} }, { u => $username, r => $c->localize('Injected') });
+                } else {
+                    push(@{ $c->stash->{Pass} }, { u => $username, r => 'Error: ' . LATMOS::Accounts::Log::lastmessage(LA_ERR) });
+                }
+            } else {
+                my $message = $account->ChangeUserPassword($username, $password, $force);
+                push(@{ $c->stash->{Pass} }, { u => $username, r => $message || $c->localize('Changed') });
+            }
+        }
+    }
+}
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Users.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Users.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Users.pm	(revision 2119)
@@ -0,0 +1,242 @@
+package LATMOS::Accounts::Web::Controller::Users;
+
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+
+use Date::Parse;
+use DateTime;
+use DateTime::TimeZone;
+use POSIX;
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Users - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index 
+
+=cut
+
+sub index : Private {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{ofilter} = $c->model('AttrFilter', 'user');
+    $c->stash->{page}{title} = "Liste des utilisateurs";
+}
+
+sub users :PathPart('users') Chained('/') CaptureArgs(1) {
+    my ( $self, $c, $username ) = @_;
+
+    $c->stash->{subform} = (split('/', ($c->req->path ||'')))[2];
+
+    my $base = $c->model('Accounts')->db;
+
+    $c->stash->{page}{title} = "Utilisateur :: $username";
+    $c->stash->{username} = $username;
+    $c->stash->{user} = $base->get_object('user', $username) or do {
+        $c->go('/no_object');
+        return;
+    };
+    $c->stash->{page}{title} = "Utilisateur :: $username";
+
+    if ($c->req->param('make_active')) {
+        $c->stash->{user}->set_c_fields('unexported' => undef);
+        $base->commit;
+    }
+    if ($c->req->param('make_inactive')) {
+        $c->stash->{user}->set_c_fields('unexported' => 1);
+        $base->commit;
+    }
+    if ($c->req->param('delete')) {
+        $base->delete_object('user', $username);
+        $base->commit;
+        $c->res->redirect('/users');
+        return;
+    }
+}
+
+sub Display :PathPart('') Chained('users') Args(0) {
+    my ( $self, $c ) = @_;
+
+    if ($c->config->{features}->{user_employment}) {
+        #Replace standard RH form by employment
+        $c->detach('/users/employment/index');
+        return 1;
+    }
+
+    $c->stash->{form} = $c->model('AttrForms', 'user', $c->stash->{user});
+    $c->stash->{form}->set_attrs;
+
+}
+
+sub Sys :PathPart('sys') Chained('users') Args(0) {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{template} = 'users/Display.tt';
+
+    $c->stash->{form} = $c->model('AttrForms', 'usersys', $c->stash->{user});
+    $c->stash->{form}->set_attrs;
+
+}
+
+sub Mail :PathPart('mail') Chained('users') Args(0) {
+    my ($self, $c) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    $c->stash->{db} = $base;
+    $c->stash->{template} = 'users/mail.tt';
+
+    if ($c->req->param('usermail')) {
+
+        my %expaliases = map { $_ => 1 } $c->req->param('expaliases');
+        foreach my $alias ($c->stash->{user}->get_attributes('aliases'), $c->stash->{user}->id) {
+            my $oalias = $base->get_object('aliases', $alias) or next;
+            if (($expaliases{ $alias } || 0) xor ($oalias->get_c_field('exported') || 0)) {
+                $oalias->set_c_fields('unexported' => ($expaliases{ $alias } ? undef : 1)) or do {
+                    $base->rollback;
+                    last;
+                };
+            }
+        }
+        $c->stash->{user}->set_c_fields(
+            mail => $c->req->param('mail') || undef,
+            otherEmail => $c->req->param('otherEmail') || undef,
+            aliases => [ grep { $_ } $c->req->param('aliases') ],
+            revaliases => $c->req->param('revaliases') || undef,
+            forward => $c->req->param('forward') || undef,
+        ) or do {
+            $base->rollback;
+            return;
+        };
+
+        $base->commit;
+    }
+}
+
+sub Password :PathPart('passwd') Chained('users') Args(0) {
+    my ($self, $c) = @_;
+    my $username = $c->stash->{username};
+
+    my $base = $c->model('Accounts')->db;
+
+    $c->stash->{template} = 'users/passwd.tt';
+    if ($c->req->param('passwd')) {
+        if ($base->check_acl($c->stash->{user}, 'userPassword', 'w')) {
+            if ($c->req->param('passwd') eq ($c->req->param('cpasswd') || '')) {
+                $c->stash->{pmerror} = $c->model('Accounts')->ChangeUserPassword(
+                    $username, $c->req->param('passwd') 
+                ) || 'Mot de passe changÃ©';
+            } else {
+                $c->stash->{pmerror} = 'Mot de passe diffÃ©rents';
+            }
+        }
+    }
+}
+
+sub Groups :PathPart('groups') Chained('users') Args(0) {
+    my ($self, $c) = @_;
+    my $username = $c->stash->{username};
+    $c->stash->{template} = 'users/groups.tt';
+
+    my $base = $c->model('Accounts')->db;
+
+    # list of group for which users can be added
+    my %ingroups = map { $_ => 1 } @{ $c->stash->{user}->get_c_field('memberOf') || []};
+    if ($c->req->param('addgroup')) {
+        $c->stash->{user}->set_c_fields('memberOf', [ keys %ingroups, $c->req->param('addgroup') ]);
+        $c->stash->{user}->base->commit;
+        # reread:
+        %ingroups = map { $_ => 1 } @{ $c->stash->{user}->get_c_field('memberOf') || []};
+    } elsif ($c->req->param('delgroup')) {
+        $c->stash->{user}->set_c_fields('memberOf',
+            [ grep { $_ ne $c->req->param('delgroup') } keys %ingroups ]
+        );
+        $c->stash->{user}->base->commit;
+        %ingroups = map { $_ => 1 } @{ $c->stash->{user}->get_c_field('memberOf') || []};
+    }
+
+    $c->stash->{othergroups} = [ grep { ! $ingroups{$_} } $base->list_objects('group') ];
+}
+
+sub AddressCommon :PathPart('') Chained('users') CaptureArgs(0) {
+    my ($self, $c) = @_;
+
+    my $username = $c->stash->{username};
+
+    my $base = $c->model('Accounts')->db;
+
+
+    if ($c->req->param('main')) {
+        if (my $ad = $base->get_object('address',
+                $c->req->param('main'))) {
+            $ad->base->commit;
+        }
+    }
+    $c->stash->{template} = 'users/address.tt';
+}
+
+sub Address :PathPart('address') Chained('AddressCommon') Args(0) {
+    my ($self, $c) = @_;
+
+    my $base = $c->model('Accounts')->db;
+    my $username = $c->stash->{username};
+
+    if ($c->req->param('del_addr')) {
+        $base->delete_object('address', $c->req->param('del_addr'));
+        $base->commit;
+    } elsif ($c->req->param('add_addr')) {
+        my $addname = $username . join('', map
+            {('a'..'z')[rand(26)]}(0..4));
+        if ($base->create_c_object('address',
+                $addname,
+                user => $username,
+            )) {
+            $base->commit;
+            $c->res->redirect($c->uri_for($username, 'address',
+                    $addname));
+            return;
+        }
+    }
+    my ($main) = $c->stash->{user}->get_attributes('otheraddress');
+    if ($main) {
+        $c->detach('AddressDisplay', [ $main ]);
+    }
+}
+
+sub AddressDisplay :PathPart('address') Chained('AddressCommon') Args(1) {
+    my ($self, $c, $arg) = @_;
+
+    my $base = $c->model('Accounts')->db;
+
+    $c->stash->{address} = $base->get_object('address', $arg);
+    $c->stash->{form} = $c->model('AttrForms', 'address',
+        $c->stash->{address});
+    $c->stash->{form}->set_attrs;
+}
+
+sub my :PathPart('my') Chained('users') Args(0) {
+    my ($self, $c) = @_;
+}
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Users/Employment.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Users/Employment.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Controller/Users/Employment.pm	(revision 2119)
@@ -0,0 +1,162 @@
+package LATMOS::Accounts::Web::Controller::Users::Employment;
+use Moose;
+use namespace::autoclean;
+
+use Date::Parse;
+use DateTime;
+use DateTime::TimeZone;
+use POSIX;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Controller::Users::Employment - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :PathPart('employment') Chained('../users') Args(0) {
+    my ( $self, $c ) = @_;
+
+    $c->stash->{template} = 'users/employment.tt';
+    my $base = $c->model('Accounts')->db;
+
+    if (my $delemp = $c->req->param('delemployment')) { 
+        $base->delete_object('employment', $delemp); 
+        $base->commit; 
+    }
+
+    if (my $employment = 
+        $c->stash->{user}->get_attributes('currentEmployment') ||
+        $c->stash->{user}->get_attributes('nextEmployment')) {
+        $c->detach('employment', [ $employment ]);
+    } else {
+        $c->forward('Emp');
+    }
+}
+
+sub Emp :PathPart('employment') Chained('../users') CaptureArgs(0) {
+    my ( $self, $c ) = @_;
+    $c->stash->{template} = 'users/employment.tt';
+    my $base = $c->model('Accounts')->db;
+    $c->stash->{newemployment} = 1;
+
+    $c->stash->{form} = $c->model('AttrForms', 'useremployment', $c->stash->{user});
+    $c->stash->{forme} = $c->model('AttrForms', 'employment', 'employment', $base);
+}
+
+sub Create :PathPart('@create') Chained('Emp') Args(0) {
+    my ($self, $c) = @_;
+
+    my $username = $c->stash->{username};
+    
+    for (1) {
+        $c->req->params->{firstday} =~ s:^(\d+)/(\d+)/(\d+):$3-$2-$1:;
+        $c->req->params->{lastday}  =~ s:^(\d+)/(\d+)/(\d+):$3-$2-$1:;
+        my $nFirstday = $c->req->param('firstday');
+        # Date::Parse understand only mm/dd/yyyy...
+        my $time = str2time($nFirstday) or last;
+        my $lastd = DateTime->from_epoch( epoch => $time )
+            ->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+        $lastd->subtract(days => 1);
+
+        my ($empl) = $c->stash->{user}->listEmployment;
+        if ($empl) {
+            my $obj = $c->model('Accounts')->db->get_object('employment', $empl);
+            # We try to truncate last employment if need
+            # if new employment if after only
+
+            if ($obj->get_attributes('firstday') lt $lastd->ymd('-')) {
+                if (my $lastday = $obj->get_attributes('lastday')) {
+                    my $firstday = $obj->get_attributes('firstday');
+                    if ($lastd->ymd('-') ge $firstday &&
+                        $lastday         ge $lastd->ymd('-')) {
+                        $obj->set_c_fields(lastday => $lastd->ymd('-'));
+                    }
+                } elsif (!$obj->get_attributes('lastday')) {
+                    $obj->set_c_fields(lastday => $lastd->ymd('-'));
+                }
+            }
+        }
+
+        my $id = $username . '-' . $lastd->ymd('-');
+        $c->req->params->{user} = $username;
+        if ($id = $c->stash->{forme}->set_attrs([ 'user',
+                    map { $_->name } $c->stash->{forme}->write_attributes], $id)) {
+            $c->res->redirect($c->uri_for('/users', $username, 'employment', $id));
+
+            $c->stash->{form}->set_attrs;
+            
+            return 1;
+        }
+    }
+}
+
+sub New :PathPart('@new') Chained('Emp') Args(0) {
+    my ($self, $c) = @_;
+}
+
+sub Duplicate :PathPart('@duplicate') Chained('Emp') Args(0) {
+    my ($self, $c) = @_;
+
+    my $oldempl = $c->model('Accounts')->db->get_object('employment',
+        $c->req->param('source')
+        ? $c->req->param('source')
+        : ($c->stash->{user}->listEmployment)[0])
+        or return;
+    foreach (qw(company managerContact contratType department)) {
+        $c->req->params->{$_} = $oldempl->get_attributes($_);
+    }
+    if (my $lastday = $oldempl->get_attributes('lastday')) {
+        my $time = DateTime->from_epoch(epoch => str2time($lastday));
+        $time->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+        $time->add( days => 1 );
+        $c->req->params->{firstday} = $time->ymd;
+    }
+    $c->stash->{forme}->set_attrs;
+}
+
+sub employment :PathPart() Chained('../users') Args(1) {
+    my ( $self, $c, $employment ) = @_;
+
+    $c->stash->{template} = 'users/employment.tt';
+    my $base = $c->model('Accounts')->db;
+
+    $c->stash->{employment} = $base->get_object('employment', $employment);
+
+    $c->stash->{form} = $c->model('AttrForms', 'useremployment', $c->stash->{user});
+    $c->stash->{form}->set_attrs;
+
+    $c->stash->{forme} = $c->model('AttrForms', 'employment', $c->stash->{employment});
+    $c->stash->{forme}->set_attrs;
+}
+
+
+
+=encoding utf8
+
+=head1 AUTHOR
+
+Olivier Thauvin,Guyancourt - B1428,+33 1 80285052,
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/I18N/fr.po
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/I18N/fr.po	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/I18N/fr.po	(revision 2119)
@@ -0,0 +1,356 @@
+# French Translation for latmos-accounts-web.
+# Copyright (C) 2016
+# This file is distributed under the same license as the latmos-account-web package.
+# Olivier Thauvin <olivier.thauvin@latmos.ipsl.fr>.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 1\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Olivier Thauvin <olivier.thauvin@latmos.ipsl.fr\n"
+"Language-Team: svp@latmos.ipsl.fr\n"
+"Language: FranÃ§ais\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. ($Dd)
+#: lib/LATMOS/Accounts/Web/Controller/About.pm:50
+#: lib/LATMOS/Accounts/Web/Controller/About.pm:64
+msgid "%1 days"
+msgstr "%1 jours"
+
+#. ($Dm)
+#: lib/LATMOS/Accounts/Web/Controller/About.pm:49
+#: lib/LATMOS/Accounts/Web/Controller/About.pm:63
+msgid "%1 months"
+msgstr "%1 mois"
+
+#. ($Dy)
+#: lib/LATMOS/Accounts/Web/Controller/About.pm:48
+#: lib/LATMOS/Accounts/Web/Controller/About.pm:62
+msgid "%1 years"
+msgstr "%1 ans"
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Employment.pm:126
+#: lib/LATMOS/Accounts/Web/Controller/Json/Employment.pm:131
+#: lib/LATMOS/Accounts/Web/Controller/Json/Employment.pm:145
+#: lib/LATMOS/Accounts/Web/Controller/Json/Employment.pm:77
+#: lib/LATMOS/Accounts/Web/Controller/Json/Expire.pm:44
+msgid "(none)"
+msgstr "(sans)"
+
+#: lib/LATMOS/Accounts/Web/Controller/Summary/Offices.pm:48
+msgid "(other)"
+msgstr "(autre)"
+
+#: root/html/includes/sidebar.tt:104
+msgid "About"
+msgstr "Ã propos"
+
+#: root/html/summary/accage/index.tt:5
+msgid "Active/Non active"
+msgstr "Actif/Non actif"
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:127
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:136
+msgid "Activity end"
+msgstr "Fin d'activitÃ©"
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:105
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:116
+msgid "Activity start"
+msgstr "DÃ©but d'activitÃ©"
+
+#: root/html/users/employment.tt:134
+msgid "Arrival"
+msgstr "ArrivÃ©e"
+
+#. ($username)
+#: lib/LATMOS/Accounts/Web/Model/Accounts.pm:181
+msgid "Cannot get user object %1"
+msgstr "Impossible de charger l'objet utilisateur %1"
+
+#: root/html/includes/sidebar.tt:15
+msgid "Change my password"
+msgstr "Changer mon mot de passe"
+
+#: lib/LATMOS/Accounts/Web/Controller/Tools/LoadPasswd.pm:61
+msgid "Changed"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:8
+msgid "Connected as"
+msgstr "ConnectÃ© en tant que"
+
+#: root/html/includes/sidebar.tt:39
+msgid "Create"
+msgstr "CrÃ©er"
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:82
+#: root/html/users/employment.tt:132
+msgid "Created"
+msgstr "CrÃ©Ã©"
+
+#: root/html/users/employment.tt:114
+msgid "Current status"
+msgstr "Statut actuel"
+
+#: root/html/summary/accage/index.tt:7 root/html/summary/employment/index.tt:6
+msgid "Department"
+msgstr "DÃ©partment"
+
+#: root/html/tools/loadpasswd/index.tt:18
+msgid "Dont check for weak passord"
+msgstr ""
+
+#: root/html/users/employment.tt:149
+msgid "Duration"
+msgstr "DurÃ©e"
+
+#: root/html/tools/loadpasswd/index.tt:15
+#, fuzzy
+msgid "Encrypted Password"
+msgstr "Mot de passe inchangÃ©"
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:96
+msgid "End of entrance"
+msgstr "Fin de circuit d'entrÃ©e"
+
+#: lib/LATMOS/Accounts/Web/Model/Accounts.pm:199
+msgid "Error while changing password"
+msgstr "Erreur lors du changement de mot de passe"
+
+#: root/html/tools/csv/index.tt:10
+msgid "Example:"
+msgstr "Exemple:"
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:87
+msgid "Expire"
+msgstr "Expiration"
+
+#: root/html/summary/employment/index.tt:12
+msgid "Filter"
+msgstr "Filtre"
+
+#: root/html/users/employment.tt:91
+msgid "From"
+msgstr "Du"
+
+#: root/html/includes/wrapper.tt:59
+msgid "IT management"
+msgstr "Gestion informatique"
+
+#: lib/LATMOS/Accounts/Web/Controller/Tools/LoadPasswd.pm:55
+#, fuzzy
+msgid "Injected"
+msgstr "ConnectÃ© en tant que"
+
+#: root/html/users/employment.tt:136
+msgid "Leave"
+msgstr "DÃ©part"
+
+#: root/html/includes/sidebar.tt:97
+msgid "Load Password"
+msgstr "Charger des mot de passe"
+
+#: root/html/includes/sidebar.tt:20
+msgid "Logout"
+msgstr "Se dÃ©connecter"
+
+#: root/html/includes/sidebar.tt:55
+msgid "Mass from CSV..."
+msgstr "Massif depuis un CSV..."
+
+#: root/html/users/employment.tt:31
+msgid "New employment"
+msgstr "Nouveau statut"
+
+#: lib/LATMOS/Accounts/Web/Controller/Tools/LoadPasswd.pm:45
+msgid "No such user"
+msgstr ""
+
+#: root/html/tools/csv/parseCSV.tt:24
+msgid "Object Name"
+msgstr "Nom de l'objet"
+
+#: root/html/tools/loadnew/index.tt:22
+msgid "Object data:"
+msgstr "DonnÃ©es de l'objet"
+
+#: root/html/tools/loadnew/index.tt:17
+msgid "Object name (optional):"
+msgstr "Nom de l'objet (optionnel):"
+
+#: root/html/tools/csv/parseCSV.tt:3
+msgid ""
+"One or multiples error occurs during objects creation, please check\n"
+"input data:"
+msgstr ""
+"Une ou plusieurs erreur sont apparues pendant la crÃ©ation des objets\n"
+"Verifier vos donnÃ©es en entrÃ©e"
+
+#: root/html/includes/sidebar.tt:87
+msgid "Others Statistics"
+msgstr "Autres Statistiques"
+
+#: root/html/users/passwd.tt:15
+msgid "Password are different"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Tools/LoadPasswd.pm:50
+msgid "Permission denied"
+msgstr ""
+
+#: root/html/users/passwd.tt:18
+#, fuzzy
+msgid "Please enter password"
+msgstr "Changer mon mot de passe"
+
+#: root/html/tools/loadpasswd/index.tt:34
+msgid "Results"
+msgstr ""
+
+#: root/html/tools/loadpasswd/index.tt:30
+msgid "Results:"
+msgstr ""
+
+#: root/html/tools/csv/index.tt:31
+msgid "Select a CSV file"
+msgstr "Selectionner un fichier CSV"
+
+#: root/html/summary/accage/index.tt:13
+msgid "Show inactive accounts"
+msgstr "Afficher les comptes inactifs"
+
+#: root/html/users/employment.tt:94
+msgid "Since"
+msgstr "A partir du"
+
+#: root/html/summary/employment/index.tt:10
+msgid "Site"
+msgstr "Site"
+
+#: root/html/summary/employment/index.tt:3
+msgid "Sort by"
+msgstr "TriÃ© par"
+
+#: root/html/summary/employment/index.tt:19
+msgid "Stacked"
+msgstr "CumulÃ©"
+
+#: root/html/includes/sidebar.tt:61 root/html/includes/sidebar.tt:76
+msgid "Statistics"
+msgstr "Statistiques"
+
+#: root/html/groups/dpmt.tt:91 root/html/summary/accage/index.tt:9
+#: root/html/summary/employment/index.tt:8
+msgid "Status"
+msgstr "Statut"
+
+#: root/html/includes/sidebar.tt:69
+msgid "Status evolution"
+msgstr "Ãvolution des status"
+
+#: root/html/tools/loadpasswd/index.tt:4
+msgid ""
+"The file format must similar to UNIX standard password, only the two\n"
+"first column are used."
+msgstr ""
+
+#: root/html/tools/csv/parseCSV.tt:18
+msgid "The follwing objects has been created:"
+msgstr "Les objets suivant ont Ã©tÃ© crÃ©Ã©s:"
+
+#: root/html/default.tt:4
+msgid "There's nothing here..."
+msgstr "Il n'y a rien ici..."
+
+#: root/html/tools/csv/index.tt:1
+msgid ""
+"This page allow you to create multiple object from information contains in\n"
+"a CSV file.\n"
+"\n"
+"The first line must contains the name of attributes, each following lines\n"
+"contains single value attribute for futur objects.\n"
+"\n"
+"For user, the object name can be omitted, object name will be built from\n"
+"firstname and lastname (sn and givenName)."
+msgstr ""
+"Cette vous permet de crÃ©er de multiples objets Ã  partir d'un fichier CSV\n"
+"\n"
+"La premiÃšre ligne de contenir la liste des attributs Ã  charger, chaque "
+"ligne\n"
+"suivante contient les valeurs pour les objets\n"
+"\n"
+"Pour les utilisateurs, le nom peut Ãªtre omis, le nom de l'objet sera "
+"construits\n"
+"Ã  partir des nom et prÃ©nom (sn et givenName)."
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:78
+msgid "Today"
+msgstr "Aujourd'hui"
+
+#: root/html/includes/sidebar.tt:93
+msgid "Tools"
+msgstr "Outils"
+
+#: root/html/tools/csv/index.tt:21
+msgid "Type of object to create"
+msgstr "Type d'objet Ã  crÃ©er"
+
+#: root/html/tools/loadnew/index.tt:4
+msgid "Type of object to create:"
+msgstr "Type d'objet Ã  crÃ©er:"
+
+#: lib/LATMOS/Accounts/Web/Model/Accounts.pm:194
+msgid "Unchanged password"
+msgstr "Mot de passe inchangÃ©"
+
+#: root/html/tools/loadpasswd/index.tt:33
+msgid "Username"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:52
+msgid "Using formated file"
+msgstr "En utilisant un fichier formatÃ©"
+
+#: root/html/includes/sidebar.tt:27
+msgid "View"
+msgstr "Voir"
+
+#. (c.model("Accounts").db.get_object("user", c.user.username))
+#: root/html/about/index.tt:3
+msgid "Welcome %1."
+msgstr "Bienvenue %1."
+
+#: root/html/tools/loadpasswd/index.tt:12
+msgid ""
+"When password are encrypted they are injected only in\n"
+"the main database. This allow users to connect to this application then "
+"changing\n"
+"their password. (only crypt MD5 is supported"
+msgstr ""
+
+#: root/html/tools/loadnew/index.tt:23 root/html/tools/loadpasswd/index.tt:21
+msgid "You can load a existing file..."
+msgstr "Vous pouvez charger un fichier existant"
+
+#. (c.stash.accountExists)
+#: root/html/about/index.tt:5
+msgid "Your account exists for %1, we hope it does not end too soon."
+msgstr "Votre compte existe depuis %1, nous espÃ©rons qu'il durera longtemps"
+
+#. (accountExpire)
+#: root/html/about/index.tt:9
+msgid "Your account will expire in %1, we are really sorry about this"
+msgstr "Votre compte exprirera dans %1, Nous vraiment dÃ©solÃ©."
+
+#: root/html/tools/loadnew/index.tt:27
+msgid "or paste the content here:"
+msgstr "ou coller le contenu ici"
+
+#: root/html/users/employment.tt:29 root/html/users/employment.tt:92
+msgid "to"
+msgstr "Ã "
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/I18N/messages.pot
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/I18N/messages.pot	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/I18N/messages.pot	(revision 2119)
@@ -0,0 +1,330 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. ($Dd)
+#: lib/LATMOS/Accounts/Web/Controller/About.pm:50 lib/LATMOS/Accounts/Web/Controller/About.pm:64
+msgid "%1 days"
+msgstr ""
+
+#. ($Dm)
+#: lib/LATMOS/Accounts/Web/Controller/About.pm:49 lib/LATMOS/Accounts/Web/Controller/About.pm:63
+msgid "%1 months"
+msgstr ""
+
+#. ($Dy)
+#: lib/LATMOS/Accounts/Web/Controller/About.pm:48 lib/LATMOS/Accounts/Web/Controller/About.pm:62
+msgid "%1 years"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Employment.pm:126 lib/LATMOS/Accounts/Web/Controller/Json/Employment.pm:131 lib/LATMOS/Accounts/Web/Controller/Json/Employment.pm:145 lib/LATMOS/Accounts/Web/Controller/Json/Employment.pm:77 lib/LATMOS/Accounts/Web/Controller/Json/Expire.pm:44
+msgid "(none)"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Summary/Offices.pm:48
+msgid "(other)"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:104
+msgid "About"
+msgstr ""
+
+#: root/html/summary/accage/index.tt:5
+msgid "Active/Non active"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:127 lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:136
+msgid "Activity end"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:105 lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:116
+msgid "Activity start"
+msgstr ""
+
+#: root/html/users/employment.tt:134
+msgid "Arrival"
+msgstr ""
+
+#. ($username)
+#: lib/LATMOS/Accounts/Web/Model/Accounts.pm:181
+msgid "Cannot get user object %1"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:15
+msgid "Change my password"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Tools/LoadPasswd.pm:61
+msgid "Changed"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:8
+msgid "Connected as"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:39
+msgid "Create"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:82 root/html/users/employment.tt:132
+msgid "Created"
+msgstr ""
+
+#: root/html/users/employment.tt:114
+msgid "Current status"
+msgstr ""
+
+#: root/html/summary/accage/index.tt:7 root/html/summary/employment/index.tt:6
+msgid "Department"
+msgstr ""
+
+#: root/html/tools/loadpasswd/index.tt:18
+msgid "Dont check for weak passord"
+msgstr ""
+
+#: root/html/users/employment.tt:149
+msgid "Duration"
+msgstr ""
+
+#: root/html/tools/loadpasswd/index.tt:15
+msgid "Encrypted Password"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:96
+msgid "End of entrance"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Model/Accounts.pm:199
+msgid "Error while changing password"
+msgstr ""
+
+#: root/html/tools/csv/index.tt:10
+msgid "Example:"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:87
+msgid "Expire"
+msgstr ""
+
+#: root/html/summary/employment/index.tt:12
+msgid "Filter"
+msgstr ""
+
+#: root/html/users/employment.tt:91
+msgid "From"
+msgstr ""
+
+#: root/html/includes/wrapper.tt:59
+msgid "IT management"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Tools/LoadPasswd.pm:55
+msgid "Injected"
+msgstr ""
+
+#: root/html/users/employment.tt:136
+msgid "Leave"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:97
+msgid "Load Password"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:20
+msgid "Logout"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:55
+msgid "Mass from CSV..."
+msgstr ""
+
+#: root/html/users/employment.tt:31
+msgid "New employment"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Tools/LoadPasswd.pm:45
+msgid "No such user"
+msgstr ""
+
+#: root/html/tools/csv/parseCSV.tt:24
+msgid "Object Name"
+msgstr ""
+
+#: root/html/tools/loadnew/index.tt:22
+msgid "Object data:"
+msgstr ""
+
+#: root/html/tools/loadnew/index.tt:17
+msgid "Object name (optional):"
+msgstr ""
+
+#: root/html/tools/csv/parseCSV.tt:3
+msgid ""
+"One or multiples error occurs during objects creation, please check\n"
+"input data:"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:87
+msgid "Others Statistics"
+msgstr ""
+
+#: root/html/users/passwd.tt:15
+msgid "Password are different"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Tools/LoadPasswd.pm:50
+msgid "Permission denied"
+msgstr ""
+
+#: root/html/users/passwd.tt:18
+msgid "Please enter password"
+msgstr ""
+
+#: root/html/tools/loadpasswd/index.tt:34
+msgid "Results"
+msgstr ""
+
+#: root/html/tools/loadpasswd/index.tt:30
+msgid "Results:"
+msgstr ""
+
+#: root/html/tools/csv/index.tt:31
+msgid "Select a CSV file"
+msgstr ""
+
+#: root/html/summary/accage/index.tt:13
+msgid "Show inactive accounts"
+msgstr ""
+
+#: root/html/users/employment.tt:94
+msgid "Since"
+msgstr ""
+
+#: root/html/summary/employment/index.tt:10
+msgid "Site"
+msgstr ""
+
+#: root/html/summary/employment/index.tt:3
+msgid "Sort by"
+msgstr ""
+
+#: root/html/summary/employment/index.tt:19
+msgid "Stacked"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:61 root/html/includes/sidebar.tt:76
+msgid "Statistics"
+msgstr ""
+
+#: root/html/groups/dpmt.tt:91 root/html/summary/accage/index.tt:9 root/html/summary/employment/index.tt:8
+msgid "Status"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:69
+msgid "Status evolution"
+msgstr ""
+
+#: root/html/tools/loadpasswd/index.tt:4
+msgid ""
+"The file format must similar to UNIX standard password, only the two\n"
+"first column are used."
+msgstr ""
+
+#: root/html/tools/csv/parseCSV.tt:18
+msgid "The follwing objects has been created:"
+msgstr ""
+
+#: root/html/default.tt:4
+msgid "There's nothing here..."
+msgstr ""
+
+#: root/html/tools/csv/index.tt:1
+msgid ""
+"This page allow you to create multiple object from information contains in\n"
+"a CSV file.\n"
+"\n"
+"The first line must contains the name of attributes, each following lines\n"
+"contains single value attribute for futur objects.\n"
+"\n"
+"For user, the object name can be omitted, object name will be built from\n"
+"firstname and lastname (sn and givenName)."
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Controller/Json/Timeline.pm:78
+msgid "Today"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:93
+msgid "Tools"
+msgstr ""
+
+#: root/html/tools/csv/index.tt:21
+msgid "Type of object to create"
+msgstr ""
+
+#: root/html/tools/loadnew/index.tt:4
+msgid "Type of object to create:"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Web/Model/Accounts.pm:194
+msgid "Unchanged password"
+msgstr ""
+
+#: root/html/tools/loadpasswd/index.tt:33
+msgid "Username"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:52
+msgid "Using formated file"
+msgstr ""
+
+#: root/html/includes/sidebar.tt:27
+msgid "View"
+msgstr ""
+
+#. (c.model("Accounts").db.get_object("user", c.user.username))
+#: root/html/about/index.tt:3
+msgid "Welcome %1."
+msgstr ""
+
+#: root/html/tools/loadpasswd/index.tt:12
+msgid ""
+"When password are encrypted they are injected only in\n"
+"the main database. This allow users to connect to this application then changing\n"
+"their password. (only crypt MD5 is supported"
+msgstr ""
+
+#: root/html/tools/loadnew/index.tt:23 root/html/tools/loadpasswd/index.tt:21
+msgid "You can load a existing file..."
+msgstr ""
+
+#. (c.stash.accountExists)
+#: root/html/about/index.tt:5
+msgid "Your account exists for %1, we hope it does not end too soon."
+msgstr ""
+
+#. (accountExpire)
+#: root/html/about/index.tt:9
+msgid "Your account will expire in %1, we are really sorry about this"
+msgstr ""
+
+#: root/html/tools/loadnew/index.tt:27
+msgid "or paste the content here:"
+msgstr ""
+
+#: root/html/users/employment.tt:29 root/html/users/employment.tt:92
+msgid "to"
+msgstr ""
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Model/Accounts.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Model/Accounts.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Model/Accounts.pm	(revision 2119)
@@ -0,0 +1,214 @@
+package LATMOS::Accounts::Web::Model::Accounts;
+
+use strict;
+use warnings;
+use FindBin;
+use lib "$FindBin::Bin/../../LATMOS-Accounts/lib";
+use base 'Catalyst::Model';
+use base 'LATMOS::Accounts';
+use LATMOS::Accounts::Log;
+
+la_set_log(
+    syslog => [],
+    console => LA_WARNING,
+);
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Model::Accounts - Catalyst Model
+
+=head1 DESCRIPTION
+
+Catalyst Model.
+
+=cut
+
+=head1 FUNCTIONS
+
+=cut
+
+sub new {
+    my ($class) = @_;
+    bless(LATMOS::Accounts
+        ->new(LATMOS::Accounts::Web->config->{config}),
+        $class);
+}
+
+sub ACCEPT_CONTEXT {
+    my ($self, $c, $form, $object, $base) = @_;
+    $self->{c} = $c;
+    $self;
+}
+
+sub accounts {
+    my ($self) = @_;
+    $self;
+}
+
+sub db {
+    my ($self) = @_;
+    $self->{_default_base} and return $self->{_default_base};
+    $self->{_default_base} = $self->base;
+    $self->{_default_base}->load or return;
+    $self->{_default_base}->wexported(1);
+    $self->{_default_base}
+}
+
+=head2 obj_to_label($ref)
+
+Return a proper label to display for each object
+
+=cut
+
+sub obj_to_label {
+    my ($self, $ref) = @_;
+    my $uri_part = {
+        user => 'Utilisateurs',
+        group => 'Groupes',
+        nethost => 'HÃŽtes rÃ©seaux',
+        netzone => 'Zones rÃ©seau',
+        site => 'Sites',
+        aliases => 'Alias',
+        service => 'Services',
+    }->{$ref};
+}
+
+sub obj_to_uri {
+    my ($self, $ref, $id) = @_;
+    my $uri_part = {
+        user => 'users',
+        group => 'groups',
+        nethost => 'nethosts',
+        netzone => 'netzones',
+        site => 'sites',
+        aliases => 'aliases',
+        service => 'services',
+    }->{$ref} || $ref;
+
+    return $self->{c}->uri_for('/', $uri_part, ($id ? $id : ()));
+}
+
+sub object_prev_next {
+    my ($self, $otype, $id) = @_;
+
+    my @list = $self->db->list_objects($otype);
+    my $prev;
+    while (@list && ($list[0] || '') ne $id) {
+        $prev = shift(@list);
+    }
+    return([ $prev, $list[1] ]);
+}
+
+sub object_navigate {
+    my ($self, $otype, $id, @filter) = @_;
+
+    my @list = $self->db->search_objects($otype, @filter);
+
+    my $i = 0;
+    for($i = 0; $i <= $#list; $i++) {
+        $list[$i] eq $id and last;
+    }
+
+    my %ptr = ();
+    $ptr{oprev} = $list[$i-1] if ($i > 0);
+    $ptr{'onext'} = $list[$i+1] if ($i < $#list);
+    $ptr{ofirst} = $list[0] if ($i > 1);
+    $ptr{'olast'} = $list[$#list] if ($i < $#list -1);
+    $ptr{list} = \@list;
+
+    return \%ptr;
+}
+
+sub CanCreateObjects {
+    my ($self, @otype) = @_;
+
+    my $db = $self->db;
+
+    if (!@otype) {
+        @otype = $db->list_supported_objects;
+    }
+
+    foreach (@otype) {
+        if (!$db->check_acl($_, '@CREATE', 'w')) {
+            return;
+        }
+    }
+    return 1;
+}
+
+# Such function must not be here, but in LATMOS::Accounts
+# But code does not allow this at time
+sub list_unowned_aliases {
+    my ($self, $filter) = @_;
+    my $db = $self->db;
+    my $sth = $db->db->prepare_cached(q{
+        select name, forward from aliases where
+        name not in (select name from "user")
+            and
+        forward not in (select array[name] from "user")
+    } . 
+    ($filter 
+        ? q{
+            and (lower(name) ILIKE $1 or
+            lower(array_to_string(forward, ',')) ILIKE $1)
+          }
+        : '')
+    );
+
+    $sth->execute($filter ? ('%' . $filter . '%') : ());
+    my %aliases;
+    while (my $res = $sth->fetchrow_hashref) {
+        $aliases{$res->{name}} = $res->{forward};
+    }
+    return \%aliases
+}
+
+sub sync_access {
+    my ($self) = @_;
+    $self->SUPER::sync_access
+}
+
+sub sync {
+    my ($self) = @_;
+    $self->default_synchro()
+}
+
+sub ChangeUserPassword {
+    my ($self, $username, $password, $force) = @_;
+
+    my $base = $self->db;
+    my $user = $base->get_object('user', $username) or do {
+        return $self->{c}->localize('Cannot get user object [_1]', $username);
+    };
+
+    my $sync = $self->sync_access;
+
+    # This is widelly burk:
+    # synchaccess do not manage connect() at time, opening a
+    # backdoor then :\
+    foreach ($sync->bases) { $_->{_user} = $base->{_user} }
+
+    my $suser = $sync->get_object_ifexists('user', $username);
+    my $msg = $user->check_password($password);
+    if ($msg ne 'ok' && !$force) {
+        return $msg . ', ' .  $self->{c}->localize('Unchanged password');
+    } elsif ($suser && $suser->_set_password($password)) {
+        $sync->commit;
+        return;
+    } else {
+        return $self->{c}->localize('Error while changing password');
+    }
+}
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Model/AttrFilter.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Model/AttrFilter.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Model/AttrFilter.pm	(revision 2119)
@@ -0,0 +1,167 @@
+package LATMOS::Accounts::Web::Model::AttrFilter;
+
+use strict;
+use warnings;
+use base 'Catalyst::Model';
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Model::AttrFilter - Catalyst Model
+
+=head1 DESCRIPTION
+
+Catalyst Model.
+
+=cut
+
+my $fields = {
+    user => [ qw(uid sn givenName description uidNumber gidNumber company department manager) ],
+    group => [ qw(cn description gidNumber sutype) ],
+    site => [ qw(l) ],
+    nethost => [ qw(name cname otherName ip macaddr owner serialNumber description) ],
+    netzone => [ qw(net group type site) ],
+    aliases => [ qw(as) ],
+};
+
+sub new {
+    my ($class) = @_;
+    return bless({}, $class);
+}
+
+sub ACCEPT_CONTEXT {
+    my ($self, $c, $otype) = @_;
+
+    my $new = {};
+    $new->{c} = $c;
+    $new->{otype} = $otype;
+    $new->{base} = $c->model('Accounts')->db;
+    $self = bless($new, __PACKAGE__);
+
+    my $base = $new->{base};
+
+    my @objlist = ();
+    if ($c->req->params->{'q'} || exists($c->req->params->{'attr'}) ) {
+        my %objs = ();
+        if ($c->req->params->{'q'}) {
+            foreach my $attr (@{ $c->config->{objects}{$otype}{quick_search} || [ 'name' ]}) {
+                foreach ($base->search_objects(
+                        $otype, "$attr~" . $c->req->params->{'q'},
+                                'oalias=NULL',
+                    )) {
+                    $objs{$_} = 1;
+                }
+                @objlist = sort keys %objs;
+            }
+        }
+
+        if (exists($c->req->params->{'attr'})) {
+            $self->{filter}{'attr'} = [ $c->req->param('attr') ];
+            $self->{filter}{'attrval'} = [ $c->req->param('attrval') ];
+            my @filter = $self->filter;
+            @objlist = $base->search_objects($otype, @filter);
+            if ($c->req->params->{'q'}) {
+                @objlist = grep { $objs{$_} } @objlist;
+            }
+        }
+    } else {
+        @objlist = $base->list_objects($otype);
+    }
+
+    $self->{objectslist} = \@objlist;
+
+    my $start = $c->req->param('start');
+    $self->{uparams} = { %{ $c->req->params || {} } };
+    my %initials;
+    my @sublist;
+    foreach (@{ $self->{objectslist} }) {
+        $initials{substr($_, 0, 1)} = 1;
+        if ($start) {
+            if (index($_, $start) == 0) {
+                push(@sublist, $_);
+            }
+        }
+    }
+    $self->{sublist} = [ @sublist ];
+    $self->{initials} = [ sort keys %initials ];
+
+    $self
+}
+
+sub otype {
+    my ($self) = @_;
+    $self->{otype};
+}
+
+sub submit {
+    my ($self) = @_;
+    return sprintf(
+        '<input type="submit" name="%s">',
+        $self->escape($self->label),
+    );
+}
+
+sub current_value {
+    my ($self) = @_;
+    my @vals = @{ $self->{filter}{attrval} || [] };
+    my @fies = @{ $self->{filter}{attr} || [] };
+    my @couples;
+    foreach (@fies) {
+        my $val = shift(@vals);
+        $_ && $val or next;
+        push(@couples, { f => $_, v => $val});
+    }
+    push(@couples, {});
+    @couples;
+}
+
+sub count_field {
+    my ($self) = @_;
+    return [ (0 .. (scalar($self->current_value) -1)) ];
+}
+
+sub search_field {
+    my ($self, $count) = @_;
+    my $form = $self->{c}->model('AttrForms', '', $self->otype, $self->{c}->stash->{db});
+    my $html = '<select name="attr">
+    <option value=""></option>';
+    my @values = $self->current_value;
+    foreach (@{ $fields->{$self->otype} }) {
+        $html .= sprintf('<option value="%s"%s>%s</option>' . "\n",
+            $_,
+            (($values[$count]->{f} || '') eq $_ ? ' selected="on"' : ''),
+            $form->attr_label($_),
+        );
+    }
+
+    $html .= '</select>';
+}
+
+sub val_field {
+    my ($self, $count) = @_;
+    my @values = $self->current_value;
+    return sprintf('<input type="text" name="attrval" value="%s">',
+        $values[$count]->{v} || '');
+}
+
+sub filter {
+    my ($self) = @_;
+    map { $_->{f} . '~' .  $_->{v} } grep { $_->{f} } $self->current_value;
+}
+
+sub objectslist {
+    my ($self) = @_;
+    $self->{objectslist};
+}
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Model/AttrForms.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Model/AttrForms.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Model/AttrForms.pm	(revision 2119)
@@ -0,0 +1,454 @@
+package LATMOS::Accounts::Web::Model::AttrForms;
+
+use strict;
+use warnings;
+use base 'Catalyst::Model';
+use LATMOS::Accounts::Log;
+use HTML::Prototype;
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Model::AttrForms - Catalyst Model
+
+=head1 DESCRIPTION
+
+Catalyst Model.
+
+=cut
+
+my $forms = {
+    usersys => {
+        name => 'SystÃšme',
+        attrs => [ qw(
+            sn givenName description
+            comment
+            managerContact
+            uid uidNumber gidNumber gecos homeDirectory loginShell
+            mail initials nickname
+            expire
+            endcircuit
+            locked
+            sshPublicKey
+            snNative
+            givenNameNative
+            wWWHomePage
+            halReference
+            createRequestId
+            create
+            createdby
+            date
+            modifiedby
+        ) ],
+    },
+    user => {
+        name => 'Statut RH',
+        attrs => [ qw(
+            sn givenName
+            description
+            company
+            employer
+            department
+            managerContact
+            manager
+            contratType
+            expire
+            endcircuit
+            create
+            createdby
+            date
+            modifiedby
+        ) ],
+    },
+    useremployment => {
+        name => 'Suivi RH',
+        attrs => [ qw(
+            sn givenName
+            expire
+        ) ]
+    },
+    useraddress => {
+        name => 'Adresse',
+        attrs => [ qw(
+            initials
+            mail
+        ) ],
+    },
+    usermy => {
+        name => 'My',
+        attrs => [ qw(
+            snNative
+            givenNameNative
+            wWWHomePage
+            halReference
+        ) ],
+    },
+    site => {
+        name => 'Site',
+        attrs => [ qw(
+            description
+            siteNick
+            streetAddress
+            postOfficeBox
+            postalCode
+            l
+            st
+            co
+            facsimileTelephoneNumber
+            create
+            date
+        ) ],
+    },
+    address => {
+        name => 'Adresse',
+        attrs => [ qw(
+            isMainAddress
+            contactOnly
+            expire
+            telephoneNumber
+            streetAddress
+            postOfficeBox
+            postalCode
+            l
+            st
+            physicalDeliveryOfficeName
+            site
+            co
+            unexported
+            description
+            facsimileTelephoneNumber
+        ) ],
+    },
+    employment => {
+        name => 'Nouvelle pÃ©riode',
+        attrs => [ qw(
+            firstday
+            lastday
+            endcircuit
+            lengthText
+            description
+            company
+            employer
+            contratType
+            department
+            managerContact
+            requestId
+        ) ],
+    },
+    group => {
+        name => 'SystÃšme',
+        attrs => [ qw(
+            gidNumber
+            description label
+            comment
+            managedBy
+            managedAlsoBy
+            autoMemberFilter
+            autoFromSutype
+            sutype
+            services
+            create
+            createdby
+            date
+            modifiedby
+        ) ],
+    },
+    nethost => {
+        name => 'Ordinateur',
+        attrs => [ qw(
+            name
+            description
+            hostType
+            comment
+            endOfWarranty
+            serialNumber
+            inventoryNumber
+            expire
+            netLocked
+            related
+            encryptKey
+            site
+            physicalDeliveryOfficeName
+            owner
+            user
+            ip
+            macaddr
+            puppetClass
+            noInheritPuppet
+            noDynamic
+            services
+            cname
+            otherName
+            reverse
+            create
+            createdby
+            date
+            modifiedby
+            unexported
+            sshfp
+        ) ],
+    },
+    netzone => {
+        name => 'Zone rÃ©seau',
+        attrs => [ qw(
+            name
+            description
+            type
+            net
+            netExclude
+            puppetClass
+            outputD
+            templateD
+            site
+            allow_dyn
+            dynFrom
+            domain
+            ipCount
+            freeIPCount
+            create
+            createdby
+            date
+            modifiedby
+            dnsRevision
+            lastUpdate
+            unexported
+            hostParams
+        ) ],
+    },
+    aliases => {
+        name => 'Alias mail',
+        attrs => [ qw(
+            expire
+            description
+            comment
+            autoMemberFilter
+            autoFromSutype
+            autoExclude
+            create
+            createdby
+            date
+            modifiedby
+            unexported
+            forward
+            finalpoint
+            parents
+        )],
+    },
+    services => {
+        name => 'Services',
+        attrs => [ qw(
+            type
+            description
+            comment
+            start
+            end
+            manager
+            dependOn
+            create
+            createdby
+            date
+            modifiedby
+            unexported
+        )],
+    },
+};
+
+sub escape {
+    my ($self, $text) = @_;
+    $text ||= '';
+    for ($text) {
+        s/&/&amp;/g;
+        s/</&lt;/g;
+        s/>/&gt;/g;
+        s/"/&quot;/g;
+    }
+    $text;
+}
+
+sub new {
+    my ($class) = @_;
+    bless({}, $class);
+}
+
+# call either OBJ or type + base
+
+sub ACCEPT_CONTEXT {
+    my ($self, $c, $form, $object, $base) = @_;
+    my $new = {};
+    $new->{c} = $c;
+    $new->{form} = $form;
+    $new->{object} = $object if (ref $object);
+    $new->{base} = $base || (ref $object ? $object->base : undef) or return $self;
+    $new->{otype} = ref $object ? $object->type : $object;
+
+
+    if ($form) {
+        foreach (@{ $forms->{$form}->{attrs} }) {
+            $new->{_attr}{$_} = $c->model('AttrFormsA', $_, (ref $object
+                    ? ($object)
+                    : ($base, $new->{otype})))
+                or die "Cannot get attribute $_";
+        }
+    }
+
+    bless($new, __PACKAGE__);
+}
+
+sub base {
+    my ( $self ) = @_;
+    $self->{base}
+}
+
+sub otype {
+    my ($self) = @_;
+    $self->{otype};
+}
+
+sub object {
+    my ($self) = @_;
+    $self->{object};
+}
+
+sub label {
+    my ($self) = @_;
+    $forms->{$self->{form}}->{name} || ''
+}
+
+sub attributes {
+    my ($self, $for) = @_;
+    grep { $_ }
+    grep { $self->base->attribute($self->otype, $_) }
+    @{ $forms->{$self->{form}}->{attrs} };
+}
+
+sub _uri_part {
+    my ($self, $ref) = @_;
+    my $uri_part = {
+        user => 'users',
+        group => 'groups',
+        nethost => 'nethosts',
+        netzone => 'netzones',
+        site => 'sites',
+        aliasess => 'aliases',
+    }->{$ref};
+}
+
+sub attr_label {
+    my ($self, $attr, $label, $hint) = @_;
+
+    if ($label) {
+        return $label;
+    } else {
+        my $oattr = $self->base->attribute($self->otype, $attr)
+            or return $attr;
+        my $label = $oattr->label;
+        utf8::encode($label);
+        return $label;
+    }
+}
+
+sub attr_hint {
+    my ($self, $attr) = @_;
+
+    $self->{_attr}{$attr}->attr_hint;
+}
+
+sub attr_field {
+    my ($self, $attr, $type) = @_;
+
+    $self->{_attr}{$attr}->attr_field($type);
+}
+
+sub field {
+    my ($self, $attr, $type) = @_;
+
+    if (!$self->{_attr}{$attr}) {
+        $self->{_attr}{$attr} = $self->{c}->model('AttrFormsA', $attr, ($self->object
+                ? ($self->object)
+                : ($self->base, $self->otype)))
+            or die "Cannot get attribute $_";
+    }
+
+    $self->{_attr}{$attr};
+}
+
+
+sub write_attributes {
+    my ($self) = @_;
+    my @attrs;
+    foreach (values %{ $self->{_attr} }) {
+        $_->readonly and next;
+        push(@attrs, $_);
+    }
+    @attrs;
+}
+
+sub set_attrs {
+    my ($self, $attrs, $id) = @_;
+    $self->{c}->req->param($self->{form}) || $attrs or return;
+    my %fields;
+    foreach ($attrs ? @{ $attrs } : $self->write_attributes) {
+        my $attr = ref $_ ? $_ : $self->{_attr}{$_};
+        $attr ||= $self->{c}->model('AttrFormsA', $_, (
+                ref $self->object
+                    ? ($self->object)
+                    : ($self->base, $self->{otype})
+                )
+        );
+
+        if ($attr->{multiple}) {
+            $fields{$attr->name} = [ grep { $_ } $self->{c}->req->param($attr->htmlname) ];
+        } else {
+            $fields{$attr->name} = $self->{c}->req->param($attr->htmlname) || undef;
+        }
+    }
+    if ($self->object) {
+        $self->object->set_c_fields(%fields) or do {
+            $self->{c}->stash->{page}{error} =
+                LATMOS::Accounts::Log::lastmessage(LA_ERR);
+            $self->object->base->rollback;
+            return;
+        };
+    } else {
+        $id ||= join('', map {('a'..'z')[rand(26)]}(0..8));
+        $self->base->create_c_object($self->otype, $id, %fields) or do {
+            $self->{c}->stash->{page}{error} =
+                LATMOS::Accounts::Log::lastmessage(LA_ERR);
+            $self->base->rollback;
+            return;
+        };
+    }
+    $self->base->commit;
+    $self->reset_param;
+    return $id || $self->object->id;
+    
+}
+
+sub reset_param {
+    my ($self) = @_;
+    foreach (values %{ $self->{_attr} }) {
+        delete($self->{c}->req->params->{$_->htmlname});
+    }
+}
+
+sub submit {
+    my ($self, $hidden) = @_;
+    return sprintf(
+        '<input type="%s" name="%s" value="Enregistrer">',
+        ($hidden ? 'hidden' : 'submit'),
+        $self->escape($self->{form}),
+    );
+}
+
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Model/AttrFormsA.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Model/AttrFormsA.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/Model/AttrFormsA.pm	(revision 2119)
@@ -0,0 +1,458 @@
+package LATMOS::Accounts::Web::Model::AttrFormsA;
+
+use strict;
+use warnings;
+use base 'LATMOS::Accounts::Bases::Attributes';
+use base 'Catalyst::Model';
+use LATMOS::Accounts::Log;
+use Date::Parse;
+use DateTime;
+use DateTime::TimeZone;
+
+=head1 NAME
+
+LATMOS::Accounts::Web::Model::AttrForms - Catalyst Model
+
+=head1 DESCRIPTION
+
+Catalyst Model.
+
+=cut
+
+sub escape {
+    my ($self, $text) = @_;
+    $text ||= '';
+    for ($text) {
+        s/&/&amp;/g;
+        s/</&lt;/g;
+        s/>/&gt;/g;
+        s/"/&quot;/g;
+    }
+    $text;
+}
+
+sub new {
+    my ($class) = @_;
+    bless({}, $class);
+}
+
+# call either OBJ or type + base
+
+sub ACCEPT_CONTEXT {
+    my ($self, $c, $attributes, $base_or_object, $maybe_otype) = @_;
+
+    my $new = LATMOS::Accounts::Bases::Attributes->new($attributes, $base_or_object, $maybe_otype);
+    $new->{c} = $c;
+    $new->{htmlname} = $attributes;
+    $new->{htmlname} =~ s/[\.-]/_/g;
+    $new->{htmlid} = $attributes;
+    $new->{htmlid} =~ s/[\.-]/_/g;
+    $new->{valcount} = 0;
+    $new->{hconfig} = LATMOS::Accounts::Web->config->{attrs}{$attributes} || {};
+    bless($new, __PACKAGE__);
+}
+
+sub htmlname {
+    $_[0]->{htmlname};
+}
+
+sub htmlid {
+    my ($self) = @_;
+
+    if ($self->{valcount}) {
+        return sprintf('%s_%03d', $self->{htmlid}, $self->{valcount});
+    } else {
+        return $_[0]->{htmlid};
+    }
+}
+
+sub htmlnewid {
+    my ($self) = @_;
+
+    $self->{valcount}++;
+
+    return $self->htmlid;
+}
+
+sub hconfig {
+    my ($self, $val) = @_;
+    $self->{hconfig}{$val}
+}
+
+sub attr_label {
+    my ($self, $label, $hint) = @_;
+    $label ||= '';
+    
+    $hint = $self->hconfig('hint');
+
+    return sprintf('<label %sfor="%s">%s</label>',
+        ($hint ? sprintf('title="%s"', $self->escape($hint)) : ''),
+        $self->escape($self->htmlname),
+        $self->escape($self->label || $self->htmlname),
+    );
+}
+
+sub attr_raw_value {
+    my ($self) = @_;
+
+    my @val = grep { $_ } (exists($self->{c}->req->params->{$self->htmlname}))
+        ? ($self->{c}->req->param($self->htmlname))
+        : $self->getValues;
+
+    if (@val) {
+        return @val;
+    } elsif(!$self->object) {
+        return (grep { $_ } ($self->default_value))
+    }
+}
+
+sub attr_field_name {
+    my ($self) = @_;
+
+    $self->htmlname;
+}
+
+sub attr_field {
+    my ($self, $type) = @_;
+
+    # Force Array context:
+    my @attr_raw_value = $self->attr_raw_value;
+    if (!$self->multiple) {
+        push(@attr_raw_value, undef) unless(@attr_raw_value);
+    }
+    my @htmls = ();
+
+    foreach my $attr_raw_value (@attr_raw_value) {
+        $self->htmlnewid;
+        push(@htmls, $self->attr_item($attr_raw_value));
+    }
+
+    if (my $js = $self->attr_js) {
+        push(@htmls,
+            qq[ <script>
+            \$( function() {
+            $js
+            });
+            </script>]
+        );
+    }
+
+    my $addempty = '';
+    if ($self->{multiple} && !$self->readonly) {
+
+        my $old = $self->{valcount} + 1;
+        $self->{valcount} = 999;
+        my $code = $self->attr_item('');
+        $code =~ s/'/\\'/g;
+        $code =~ s/\n/ /gm;
+
+        $addempty = sprintf('<div id="%s_div"></div>', $self->htmlid);
+        $addempty .= "\n";
+        $addempty .= sprintf(q|
+            <button id="%s_add" type="button" style="display:block">+</button>
+            <script type="text/javascript">
+            var cn_%s = %d;
+            $( '#%s_add' ).click(function(even, ui) {
+                $('#%s_div').append('%s'.replace(/%d/g, cn_%s)).append('<br>');
+
+                cn_%s++;
+                %s
+            });
+            </script>
+            |,
+            $self->htmlid,
+            $self->htmlid,
+            $old,
+            $self->htmlid,
+            $self->htmlid,
+            $code,
+            $self->{valcount},
+            $self->htmlid,
+            $self->htmlid,
+            $self->attr_js,
+        );
+
+        $addempty .= "\n";
+
+    }
+
+    return join("<br>\n", @htmls) . $addempty;
+}
+
+sub attr_item {
+    my ($self, $value) = @_;
+
+    my $html = '';
+
+    my $type = 'attr_' . uc($self->form_type);
+
+    if ($self->can($type)) {
+        $html .= $self->$type($value);
+    } else {
+        $html .= $self->attr_TEXT($value);
+    }
+
+    $html .= q|<span class="inputvalidate" id="|. $self->htmlid . q|_check"></span>|;
+
+    if (my $ref = $self->reference) {
+        my $uri_part = $self->_uri_part($ref);
+        if ($self->base->attribute($ref, 'displayName')) {
+            my $text = '';
+            if ($value &&
+                (my $obj = $self->base->get_object($ref, $value))) {
+                $text = $obj->get_attributes('displayName');
+            }
+            #$html .= sprintf(
+                #qq|<span id="${htmlid}_ref" style="margin-left: 1em">$text</span>|,
+                #   $self->escape($text),
+                #);
+        } elsif($value && $uri_part) {
+            $html .= sprintf(
+                '<a href="%s"><img class="attr" src="%s" title="%s"></a>',
+                $self->{c}->uri_for('/', $uri_part, $value),
+                $self->{c}->uri_for('/static', 'images', 'arrow-right.png'),
+                $value,
+            );
+        }
+    }
+
+    $html
+}
+
+sub attr_js {
+    my ($self, $value) = @_;
+
+    my $type = 'attr_' . uc($self->form_type) . '_js';
+
+    if ($self->can($type)) {
+        return $self->$type($value);
+    } else {
+        return '';
+    }
+}
+
+
+sub attr_TEXT {
+    my ($self, $value, $name, $id) = @_;
+
+    return sprintf(
+        '<input type="text" id="%s" name="%s" value="%s" size="%d">',
+        $self->escape($id   || $self->htmlid),
+        $self->escape($name || $self->htmlname),
+        $self->escape($value),
+        $self->form_option('length') || 20,
+    );
+
+}
+
+sub attr_TEXT_js {
+    my ($self) = @_;
+
+    my $html = '';
+
+    if ($self->uniq) {
+        $html .= sprintf(q|
+                $( '[name="%s"]' ).on('input', function() {
+                    var id = $(this).attr('id');
+                    $.ajax({
+                        method: "POST",
+                        url: '%s',
+                        data: { val: $(this).val() },
+                    }). done(function( msg ) {
+                        $( '#' + id + '_check' ).html (msg);
+                    });
+                    return false;
+                });
+        |,
+        $self->htmlname,
+        $self->{c}->uri_for('/ajax', 'objattrexist', $self->otype, $self->name),
+        );
+    }
+
+    $html;
+}
+
+sub attr_LABEL {
+    my ($self, $value) = @_;
+
+    if ($self->real_form_type =~ m/(DATE|DATETIME)/) {
+        if (my $time = str2time($value)) {
+            my $dt = DateTime->from_epoch(epoch => $time);
+            $dt->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+            $value = $dt->dmy('/') . ($self->real_form_type eq 'DATE'
+                ? ''
+                : ' ' . $dt->hms(':'));
+        }
+    }
+
+    return $self->escape($value);
+}
+
+sub attr_TEXTAREA {
+    my ($self, $value) = @_;
+    return sprintf(
+        '<textarea id="%s" name="%s" cols="40">%s</textarea>',
+        $self->escape($self->htmlid),
+        $self->escape($self->htmlname),
+        $self->escape($value || ''),
+    );
+}
+
+
+sub attr_DATE {
+    my ($self, $value, $name, $id) = @_;
+
+    if (my $time = str2time($value)) {
+        my $dt = DateTime->from_epoch(epoch => $time);
+        $dt->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+        $value = $dt->dmy('/')
+    }
+
+    return sprintf(
+        '<input type="text" id="%s" name="%s" value="%s" size="%d">',
+        $self->escape($id   || $self->htmlid),
+        $self->escape($name || $self->htmlname),
+        $self->escape($value),
+        $self->form_option('length') || 20,
+    );
+}
+
+sub attr_DATE_js {
+    my ($self, $value) = @_;
+    return sprintf(q|
+        $('[name="%s"]').datepicker( {
+            changeMonth: true,
+            changeYear: true,
+            dateFormat: 'dd/mm/yy'
+        } );
+    |, $self->escape($self->htmlname));
+}
+
+sub attr_DATETIME {
+    my ($self, $value, $name, $id) = @_;
+
+    if (my $time = str2time($value)) {
+        my $dt = DateTime->from_epoch(epoch => $time);
+        $dt->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+        $value = $dt->dmy('/') . ' ' . $dt->hms(':')
+    }
+
+    return sprintf(
+        '<input type="text" id="%s" name="%s" value="%s" size="%d">',
+        $self->escape($id   || $self->htmlid),
+        $self->escape($name || $self->htmlname),
+        $self->escape($value),
+        $self->form_option('length') || 20,
+    );
+}
+
+sub attr_DATETIME_js {
+    my ($self, $value) = @_;
+    return sprintf(q|
+        $('[name="%s"]').datepicker( {
+            changeMonth: true,
+            changeYear: true,
+            dateFormat: 'dd/mm/yy'
+        } );
+    |, $self->escape($self->htmlname));
+}
+
+sub attr_CHECKBOX {
+    my ($self, $value) = @_;
+    $value ||= '';
+
+    return sprintf('<input type="checkbox" id="%s" name="%s"%s>%s',
+        $self->escape($self->htmlid),
+        $self->escape($self->htmlname),
+        ($value ? '  checked="yes"' : ''),
+        ($self->form_option('rawvalue') ? " $value" : ''),
+    );
+}
+
+sub attr_LIST {
+    my ($self, $value) = @_;
+
+    my @values = $self->can_values;
+    $value ||= '';
+
+    if (@values < 5) {
+        return sprintf(q|<select id="%s" name="%s">%s%s</select>|,
+            $self->htmlid,
+            $self->htmlname,
+            ($self->mandatory ? '' : '<option value="">--</option>'),
+            join(",\n", map { $_ = sprintf('<option value="%s"%s>%s</option>', $_, ($_ eq $value ? ' selected="selected"' : ''), $self->display($_)) } sort $self->can_values),
+        );
+    } else {
+        my $html = $self->attr_TEXT($self->display($value));
+    }
+}
+
+sub attr_LIST_js {
+    my ($self) = @_;
+    return sprintf(q|
+            $('[name="%s"]').autocomplete({
+                delay: 200,
+                minLength: 0,
+                source: '%s',
+            })
+            .autocomplete( "instance" )._renderItem = function( ul, item ) {
+                return $( "<li>" )
+                .append( "<a>" + item.label + " (" + item.value +  ")</a>" )
+                .appendTo( ul );
+            };
+        |,
+        $self->htmlname,
+        $self->{c}->uri_for('/json/refval', $self->otype, $self->name),
+    );
+}
+
+sub _uri_part {
+    my ($self, $ref) = @_;
+    my $uri_part = {
+        user => 'users',
+        group => 'groups',
+        nethost => 'nethosts',
+        netzone => 'netzones',
+        site => 'sites',
+        aliasess => 'aliases',
+    }->{$ref};
+}
+
+sub _attr_js_item {
+    my ($self, $value, $htmlid) = @_;
+
+    $htmlid = $self->htmlid;
+    my (@js);
+
+    if (my $ref = $self->reference) {
+        my $uri_part = $self->_uri_part($ref);
+        if ($self->base->attribute($ref, 'displayName')) {
+            push(@js, 
+                $self->{c}->prototype->observe_field( $htmlid,
+                    {
+                        update => "${htmlid}_ref",
+                        url => $self->{c}->uri_for('/ajax', 'rawattr', $ref),
+                        frequency => 1,
+                        with   => "'attr=displayName&id=' + element.options[element.selectedIndex].text",
+                    }
+                )
+            ) if ($self->form_type =~ /list/i);
+        }
+    }
+
+    return(join("\n", @js));
+}
+
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/View/Json.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/View/Json.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/View/Json.pm	(revision 2119)
@@ -0,0 +1,48 @@
+package LATMOS::Accounts::Web::View::Json;
+
+use strict;
+use base 'Catalyst::View::JSON';
+use utf8;
+
+__PACKAGE__->config(
+    allow_callback  => 1,    # defaults to 0
+    callback_param  => 'cb', # defaults to 'callback'
+    expose_stash    => 'data', # defaults to everything
+);
+
+use JSON::XS ();
+
+sub encode_json {
+    my($self, $c, $data) = @_;
+    my $encoder = JSON::XS->new->pretty->allow_nonref;
+    my $res = $encoder->encode($data);
+    # This fix an issue on old Catalyst...
+    utf8::decode($res);
+    $res
+}
+
+
+=head1 NAME
+
+LATMOS::Accounts::Web::View::Json - Catalyst JSON View
+
+=head1 SYNOPSIS
+
+See L<LATMOS::Accounts::Web>
+
+=head1 DESCRIPTION
+
+Catalyst JSON View.
+
+=head1 AUTHOR
+
+olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/View/TT.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/View/TT.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/View/TT.pm	(revision 2119)
@@ -0,0 +1,39 @@
+package LATMOS::Accounts::Web::View::TT;
+
+use strict;
+use base 'Catalyst::View::TT';
+use LATMOS::Accounts::Web;
+
+__PACKAGE__->config(
+    TEMPLATE_EXTENSION => '.tt',
+    INCLUDE_PATH => [
+        LATMOS::Accounts::Web->path_to( 'root', 'html' ),
+    ],
+    PRE_PROCESS  => 'includes/defs.tt',
+    WRAPPER      => 'includes/wrapper.tt',
+);
+
+=head1 NAME
+
+LATMOS::Accounts::Web::View::TT - TT View for LATMOS::Accounts::Web
+
+=head1 DESCRIPTION
+
+TT View for LATMOS::Accounts::Web. 
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Web>
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/View/TTpart.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/View/TTpart.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web/View/TTpart.pm	(revision 2119)
@@ -0,0 +1,38 @@
+package LATMOS::Accounts::Web::View::TTpart;
+
+use strict;
+use base 'Catalyst::View::TT';
+use LATMOS::Accounts::Web;
+
+__PACKAGE__->config(
+    TEMPLATE_EXTENSION => '.tt',
+    INCLUDE_PATH => [
+    LATMOS::Accounts::Web->path_to( 'root', 'html' ),
+    ],
+    PRE_PROCESS  => 'includes/defs.tt',
+);
+
+=head1 NAME
+
+LATMOS::Accounts::Web::View::TTpart - TT View for LATMOS::Accounts::Web
+
+=head1 DESCRIPTION
+
+TT View for LATMOS::Accounts::Web. 
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Web>
+
+=head1 AUTHOR
+
+Thauvin Olivier
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/about/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/about/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/about/index.tt	(revision 2119)
@@ -0,0 +1,25 @@
+<div class="objectform">
+
+<p>[% l('Welcome [_1].',  c.model('Accounts').db.get_object('user', c.user.username )) | html %]</p>
+
+<p>[% l('Your account exists for [_1], we hope it does not end too soon.',
+    c.stash.accountExists) %]</p>
+
+[% IF accountExpire %]
+<p>[% l('Your account will expire in [_1], we are really sorry about this', accountExpire) %]
+[% END %]
+
+<h2>LATMOS::Accounts::Web</h2>
+
+<p>(c) LATMOS / CNRS / IPSL</p>
+
+<p>Auteur: Olivier Thauvin</p>
+
+<h3>Contributeurs</h3>
+
+<p>Remerciement particuliers Ã  tout ceux qui ont participÃ© Ã  ce projet:</p>
+
+<p>Philippe Weill, Yann Delcambre</p>
+</div>
+
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/check_new_name.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/check_new_name.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/check_new_name.tt	(revision 2119)
@@ -0,0 +1,4 @@
+[% IF validity %]
+<img src="[% c.uri_for('/static', 'images', 'dialog-cancel.png') %]">
+[% validity %]
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/check_ug_validity.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/check_ug_validity.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/check_ug_validity.tt	(revision 2119)
@@ -0,0 +1,4 @@
+[% IF validity %]
+<img src="[% c.uri_for('/static', 'images', 'dialog-cancel.png') %]">
+([% validity %])
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/cracklib.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/cracklib.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/cracklib.tt	(revision 2119)
@@ -0,0 +1,5 @@
+[% IF cracklib != 'ok' %]
+[% cracklib %]
+[% ELSE %]
+<input type="submit" value="Valider">
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/displayattr.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/displayattr.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/displayattr.tt	(revision 2119)
@@ -0,0 +1,1 @@
+[% value | html %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/list_objects.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/list_objects.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/list_objects.tt	(revision 2119)
@@ -0,0 +1,3 @@
+<!-- $Id$ -->
+
+[% INCLUDE 'includes/list_objects.tt' %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/objattrexist.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/objattrexist.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/objattrexist.tt	(revision 2119)
@@ -0,0 +1,6 @@
+[% IF exists %]
+<img src="[% c.uri_for('/static', 'images', 'dialog-cancel.png') %]">
+([% exists %])
+[% ELSE %]
+<img src="[% c.uri_for('/static', 'images', 'dialog-ok-apply.png') %]">
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/objexist.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/objexist.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/objexist.tt	(revision 2119)
@@ -0,0 +1,10 @@
+[% IF c.req.param('val') %]
+[% IF exists %]
+<img class="attr" src="[% c.uri_for('/static', 'images', 'dialog-cancel.png') %]">
+([% exists %])
+[% ELSE %]
+<img class="attr" src="[% c.uri_for('/static', 'images', 'dialog-ok-apply.png') %]">
+[% END %]
+[% ELSE %]
+<!-- No input value -->
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/random_password.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/random_password.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/random_password.tt	(revision 2119)
@@ -0,0 +1,1 @@
+[% random_password | html %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/rawattr.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/rawattr.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/rawattr.tt	(revision 2119)
@@ -0,0 +1,1 @@
+[% value | html %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/select_freeip_select.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/select_freeip_select.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/select_freeip_select.tt	(revision 2119)
@@ -0,0 +1,12 @@
+<!-- $Id$ -->
+<select name="newip">
+<option value="any">IP alÃ©atoire</option>
+<option value="first">PremiÃšre</option>
+
+<optgroup label="IPs">
+[% FOREACH ip = c.model('Accounts').db.get_object('netzone',
+c.req.param('zone')).get_attributes('freeIP') %]
+<option value="[% ip | html %]">[% ip | html %]</option>
+[% END %]
+</optgroup>
+</select>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/select_objects_list.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/select_objects_list.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/select_objects_list.tt	(revision 2119)
@@ -0,0 +1,3 @@
+<!-- $Id$ -->
+
+[% INCLUDE 'includes/select_objects_list.tt' %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/set_attrs.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/set_attrs.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/set_attrs.tt	(revision 2119)
@@ -0,0 +1,1 @@
+<!-- $Id$ -->
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/user_to_group_form.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/user_to_group_form.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/ajax/user_to_group_form.tt	(revision 2119)
@@ -0,0 +1,23 @@
+<!-- $Id -->
+[% modallow = c.model('Accounts').db.check_acl(user, 'memberOf', 'w') %]
+
+[% FOREACH g = othergroups %]
+[% IF loop.first %]
+<ul>
+[% END %]
+<li>
+[% IF modallow %]
+<form action="[% c.uri_for('/users', user.id, 'groups') %]" method="POST">
+<input type="hidden" name="addgroup" value="[% g | html %]">
+<input type="image" src="[% c.uri_for('/static', 'images', 'group_add.png')
+%]" width="24" height="24">
+[% END %]
+<a href="[% c.uri_for('/groups', g) %]">[% g | html %]</a><br>
+[% IF modallow %]
+</form>
+[% END %]
+</li>
+[% IF loop.last %]
+</ul>
+[% END %]
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/aliases/default.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/aliases/default.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/aliases/default.tt	(revision 2119)
@@ -0,0 +1,46 @@
+<!-- $Id$ -->
+
+[% INCLUDE 'includes/object_header.tt'
+       headerobj = aliases
+    %]
+
+<div id="oaction">
+[% IF c.model('Accounts').db.check_acl(aliases, 'exported', 'w') %]
+    [% IF NOT aliases.get_c_field('exported') %]
+    <form method="POST" action="[% c.uri_for('/aliases', aliasesname, subform) %]"
+          onSubmit="return confirm('Activer l\'alias mail [% aliasesname %] ?') ? true : false">
+    <input type="image" name="activate" value="Activer" title="Activer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-enable.png') %]">
+    <input type="hidden" value="activer" name="make_active">
+    </form>
+    [% ELSE %]
+    <form method="POST" action="[% c.uri_for('/aliases', aliasesname, subform) %]"
+          onSubmit="return confirm('DÃ©sactiver l\'alias mail [% aliasesname %] ?') ? true : false">
+    <input type="image" name="disable" value="DÃ©sactiver" title="DÃ©sactiver"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-disable.png') %]">
+    <input type="hidden" value="dÃ©sactiver" name="make_inactive">
+    </form>
+    [% END %]
+[% END %]
+[% IF c.model('Accounts').db.check_acl(aliases, 'DELETE', 'w') %]
+    <form method="POST" action="[% c.uri_for('/aliases', aliasesname, subform) %]"
+          onSubmit="return confirm('DÃ©truire l\'alias mail [% aliasesname %] ?') ? true : false">
+    <input type="image" name="delete" value="Supprimer" title="Supprimer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-delete.png') %]">
+    <input type="hidden" value="DETRUIRE" name="delete">
+    </form>
+[% END %]
+</div>
+
+<div id="objectform" class="objectform">
+
+[% IF c.model('Accounts').db.check_acl('aliases', 'forward', 'w') %]
+
+[% INCLUDE 'includes/form.tt' %]
+
+</div>
+
+[% ELSE %]
+<p>Vous n'avez pas la permission d'acceder Ã  cette page.</p>
+[% END %] [% # ACLS %]
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/aliases/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/aliases/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/aliases/index.tt	(revision 2119)
@@ -0,0 +1,10 @@
+<!-- $Id$ -->
+<div id="filter">
+[% INCLUDE 'includes/filter.tt'
+   formuri = c.uri_for()
+%]
+</div>
+
+<div id="list_objects">
+[% INCLUDE 'includes/list_objects.tt' %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/component_disabled.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/component_disabled.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/component_disabled.tt	(revision 2119)
@@ -0,0 +1,1 @@
+<p>NO ACCESS</p>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/create/default.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/create/default.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/create/default.tt	(revision 2119)
@@ -0,0 +1,45 @@
+<!-- $Id$ -->
+
+<h2>CrÃ©er un objet de type [% c.forward('/obj_to_label', [ form.otype ]) | html %]</h2>
+<div id="objectform" class="objectform">
+<form action="[% c.uri_for('/create', form.otype) %]" method="POST">
+<table border="1">
+
+<input type="hidden" name="step" value="[% ochelper.step | html %]">
+[% IF ochelper.name.ask %]
+<tr><th>Nom de l'objet:</th>
+<td><input id="_name" type="text" name="_name" value="[% ochelper.name.content | html %]">
+<script>
+$( function() {
+    $( "#_name" ).on('input', function() {
+        $.ajax({
+            method: "POST",
+            url: '[% c.uri_for('/ajax', 'objexist', form.otype) %]',
+            data: { val: $(this).val() },
+        }). done(function( msg ) {
+            $( '#_name_span' ).html (msg);
+        });
+    });
+});
+</script>
+<span id="_name_span"></span>
+</td></tr>
+[% END %]
+[% FOREACH attr = ochelper.ask %]
+<tr>
+<th>
+    [% form.attr_label(attr) %]
+    [% IF form.attr_hint(attr) %]
+    <span
+        title="[% form.attr_hint(attr) | html %]"
+        class="hint">?</span>
+    [% END %]
+</th>
+<td>[% form.attr_field(attr) %]</td></tr>
+[% END %]
+
+<tr><td colspan="2"><input type="submit"></td></tr>
+</table>
+
+</form>
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/default.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/default.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/default.tt	(revision 2119)
@@ -0,0 +1,5 @@
+<!-- $Id$ -->
+
+<div class=error style="border: solid; width:25%; padding: 2em; margin-left:auto; margin-right:auto;">
+[% l('There\'s nothing here...') %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/default.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/default.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/default.tt	(revision 2119)
@@ -0,0 +1,14 @@
+<!-- $Id$ -->
+[% IF NOT group %]
+No group [% groupname | html %] found.
+[% ELSE %]
+    
+[% INCLUDE 'groups/object_header.tt' %]
+
+<div class="objectform" id="objectform">
+[% INCLUDE 'includes/form.tt'
+    form = form
+    form_uri = c.uri_for(groupname, subform) %]
+</div>
+</div>
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/dpmt.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/dpmt.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/dpmt.tt	(revision 2119)
@@ -0,0 +1,106 @@
+<!-- $Id$ -->
+[% IF NOT group %]
+No group [% groupname | html %] found.
+[% ELSE %]
+
+[% INCLUDE 'groups/object_header.tt' %]
+
+<div class="objectform" id="objectform">
+
+<div style="margin: 2em; border: outset; width: auto; display: inline-block">
+<form action="[% c.uri_for(groupname, 'dpmt') %]">
+<input id="inactive" type="checkbox" name="inactive" value="1" [% "checked" IF c.req.param('inactive') %]>
+<label for="inactive">Inclure les comptes inactifs</label><br>
+<input id="mail" type="checkbox" name="mail" value="1" [% "checked" IF c.req.param('mail') %]>
+<label for="mail">Afficher les mails</label><br>
+
+[% FOREACH co = c.model('Accounts').db.attributes_summary('user', 'company').sort %]
+[% IF loop.first %]
+SociÃ©tÃ©:
+<select name="company">
+<option value="">--</option>
+[% END %]
+<option value="[% co | html %]" [% 'selected="selected"' IF co ==
+c.req.param('company') %]>[% co | html %]</option>
+[% IF loop.last %]
+</select>
+<br>
+[% END %]
+[% END %]
+
+<input type="submit">
+</form>
+[% IF odpmt.get_attributes('managedBy') %]
+[% ou = c.model('Accounts').db.get_object('user',
+odpmt.get_attributes('managedBy')) %]
+<p>Gestionnaire:
+    <a href="[% c.uri_for('/users', ou.id) %]">
+    [% ou.get_attributes('displayName') | html %]
+</a>
+</p>
+<p>[% user.size %] personnes dans ce dÃ©partement, [% guser.size %]
+associÃ©s.</p>
+[% END %]
+
+<hr>
+
+[% FOREACH u = user %]
+[% ou = c.model('Accounts').db.get_object('user', u) %]
+<a href="[% c.uri_for('/users', u) %]">[% ou.get_attributes('displayName') | html %]</a>
+([% ou.get_attributes('contratType') | html %])
+[% IF c.req.param('mail') %]
+&lt;[% ou.get_attributes('mail') | html %]&gt;
+[% END %]
+<br>
+[% END %]
+
+<p>AssociÃ©s au dÃ©partement</p>
+
+[% FOREACH u = guser %]
+[% ou = c.model('Accounts').db.get_object('user', u) %]
+<a href="[% c.uri_for('/users', u) %]">[% ou.get_attributes('displayName') | html %]</a>
+(<a href="[% c.uri_for('/groups', ou.get_attributes('department'), 'dpmt') %]">[%
+ou.get_attributes('department') %]</a>)
+[% IF c.req.param('mail') %]
+&lt;[% ou.get_attributes('mail') | html %]&gt;
+[% END %]
+<br>
+[% END %]
+</div>
+
+<div id=chartdpmt style="display: inline-block; width: 600px; height: 500px;">
+</div>
+
+<script>
+$(document).ready(function(){
+  // Our ajax data renderer which here retrieves a text file.
+  // it could contact any source and pull data, however.
+  // The options argument isn't used in this renderer.
+
+    $.ajax({
+      // have to use synchronous here, else the function
+      // will return before the data is fetched
+      // async: false,
+      url: "[% c.uri_for('/json/dpmtrh', dpmt) %]",
+      dataType:"json",
+      success: function(data) {
+          var plot2 = $.jqplot('chartdpmt',
+            [ data ],
+
+            {
+                title: '[% l('Status') %]',
+                seriesDefaults: {
+                    shadow: false,
+                    renderer: jQuery.jqplot.PieRenderer,
+                    rendererOptions: { padding: 2, sliceMargin: 2, showDataLabels: true }
+            },
+            legend: { show:true, location: 'e' },
+          });
+        }
+    });
+    $.jqplot.config.enablePlugins = true;
+});
+</script>
+
+</div>
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/index.tt	(revision 2119)
@@ -0,0 +1,10 @@
+<!-- $Id$ -->
+<div id="filter">
+[% INCLUDE 'includes/filter.tt'
+   formuri = c.uri_for()
+%]
+</div>
+
+<div id="list_objects">
+[% INCLUDE 'includes/list_objects.tt' %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/menu.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/menu.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/menu.tt	(revision 2119)
@@ -0,0 +1,17 @@
+<!-- $Id$ -->
+
+[% IF groupname %]
+<h3>
+<a href="[% c.uri_for(groupname) %]">GÃ©rer le groupe [% username | html
+%]</a>
+</h3>
+
+<ul>
+        <li><a href="[% c.uri_for(groupname) %]"><span [% 'id="oactive"' IF subform == '' %]>SystÃšme</span></a></li>
+        <li><a href="[% c.uri_for(groupname, 'users') %]"><span [% 'id="oactive"' IF subform == 'users' %]>Utilisateurs</span></a></li>
+[% IF group.get_attributes('sutype') == 'dpmt' %]<li>
+        <li><a href="[% c.uri_for(groupname, 'dpmt') %]"><span [% 'id="oactive"' IF subform == 'dpmt' %]>DÃ©partement</span></a></li>
+[% END %]
+</ul>
+
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/object_header.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/object_header.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/object_header.tt	(revision 2119)
@@ -0,0 +1,42 @@
+<!-- $Id$ -->
+
+[% INCLUDE 'includes/object_header.tt'
+    headerobj = group
+    %]
+
+<div id="oaction">
+[% IF c.model('Accounts').db.check_acl(group, 'exported', 'w') %]
+    [% IF NOT group.get_c_field('exported') %]
+    <form method="POST" action="[% c.uri_for('/groups', groupname, subform) %]"
+          onSubmit="return confirm('Activer le groupe [% groupname %] ?') ? true : false">
+    <input type="image" name="make_active" value="Activer" title="Activer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-enable.png') %]">
+    <input type="hidden" value="activer" name="make_active">
+    </form>
+    [% ELSE %]
+    <form method="POST" action="[% c.uri_for('/groups', groupname, subform) %]"
+          onSubmit="return confirm('DÃ©sactiver le group [% groupname %] ?') ? true : false">
+    <input type="image" name="make_inactive" value="DÃ©sactiver" title="DÃ©sactiver"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-disable.png') %]">
+    <input type="hidden" value="dÃ©sactiver" name="make_inactive">
+    </form>
+    [% END %]
+[% END %]
+[% IF c.model('Accounts').db.check_acl(group, 'DELETE', 'w') %]
+    <form method="POST" action="[% c.uri_for('/groups', groupname, subform) %]"
+          onSubmit="return confirm('DÃ©truire le groupe [% groupname %] ?') ? true : false">
+    <input type="image" name="delete" value="Supprimer" title="Supprimer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-delete.png') %]">
+    <input type="hidden" value="DETRUIRE" name="delete">
+    </form>
+[% END %]
+</div>
+
+<div id="omenu">
+        <a href="[% c.uri_for(groupname) %]"><span [% 'id="oactive"' IF subform == '' %]>SystÃšme</span></a>
+        <a href="[% c.uri_for(groupname, 'users') %]"><span [% 'id="oactive"' IF subform == 'users' %]>Utilisateurs</span></a>
+[% IF group.get_attributes('sutype') == 'dpmt' %]
+        <a href="[% c.uri_for(groupname, 'dpmt') %]"><span [% 'id="oactive"' IF subform == 'dpmt' %]>DÃ©partement</span></a>
+[% END %]
+</div>
+<div style="clear: both;"></div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/users.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/users.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/groups/users.tt	(revision 2119)
@@ -0,0 +1,109 @@
+<!-- $Id$ -->
+[% IF NOT group %]
+No group [% groupname | html %] found.
+[% ELSE %]
+    
+[% modallow = c.model('Accounts').db.check_acl(group, 'memberUID', 'w') AND
+NOT group.attribute('memberUID').ro %]
+
+[% INCLUDE 'groups/object_header.tt' %]
+
+<div class="objectform" id="objectform">
+<table border=1>
+<tr><th>Membres:</th><th>Ajouter:</th></tr>
+<tr>
+<td valign="top">
+[% FOREACH uid = c.model('Accounts').db.search_objects('user', 'oalias=NULL', 'gidNumber=' _
+group.get_c_field('gidNumber')).sort %]
+[% IF loop.first %]
+<b>Membres primaire:</b><br>
+[% END %]
+<a href="[% c.uri_for('/users', uid) %]">[% uid | html %]</a>
+[% ouser = c.model('Accounts').db.get_object('user', uid) %]
+[% INCLUDE user_flag ouser=ouser %]
+<br>
+[% END %]
+
+[% FOREACH uid = c.model('Accounts').db.search_objects('user', 'oalias=NULL', 'memberOf='
+_ group.id, 'active=1') %]
+
+[% ouser = c.model('Accounts').db.get_object('user', uid) %]
+[% IF loop.first %]
+<b>Membres</b><br>
+[% END %]
+[% IF modallow %]
+[% IF ouser.get_attributes('department') == group.id OR
+      ouser.get_attributes('contratType') == group.id %]
+<img src="[% c.uri_for('/static', 'images', 'changes-prevent.png') %]"
+    width="24" height="24" title="Ce groupe est gÃ©rÃ© via un autre attribut">
+[% ELSE %]
+<form action="[% c.uri_for(groupname, subform) %]" method="POST" style="display: inline">
+<input type="hidden" name="deluser" value="[% uid | html %]">
+<input type="image" src="[% c.uri_for('/static', 'images', 'group_del.png') %]" width="24" height="24">
+</form>
+[% END %]
+[% END %]
+<a href="[% c.uri_for('/users', uid) %]">[% uid | html %]
+([% ouser.get_attributes('displayName') | html %])</a>
+[% INCLUDE user_flag ouser=ouser %]
+<br>
+[% END %]
+
+[% FOREACH uid = c.model('Accounts').db.search_objects('user', 'oalias=NULL', 'memberOf='
+_ group.id, 'active=0') %]
+
+[% ouser = c.model('Accounts').db.get_object('user', uid) %]
+[% IF loop.first %]
+<b>Membres Inactifs</b><br>
+[% END %]
+[% IF modallow %]
+[% IF ouser.get_attributes('department') == group.id OR
+      ouser.get_attributes('contratType') == group.id %]
+<img src="[% c.uri_for('/static', 'images', 'changes-prevent.png') %]"
+    width="24" height="24" title="Ce groupe est gÃ©rÃ© via un autre attribut">
+[% ELSE %]
+<form action="[% c.uri_for(groupname, subform) %]" method="POST" style="display: inline">
+<input type="hidden" name="deluser" value="[% uid | html %]">
+<input type="image" src="[% c.uri_for('/static', 'images', 'group_del.png') %]" width="24" height="24">
+</form>
+[% END %]
+[% END %]
+<a href="[% c.uri_for('/users', uid) %]">[% uid | html %]
+([% ouser.get_attributes('displayName') | html %])</a>
+[% INCLUDE user_flag ouser=ouser %]
+<br>
+[% END %]
+
+</td>
+
+
+<td valign="top">
+[% IF modallow %]
+<form action="[% c.uri_for(groupname, 'users') %]" method="POST">
+<input type=text id="adduser" name="adduser">
+<input type="submit" value="Ajouter">
+</form>
+
+<script>
+$(function() {
+    $('#adduser').autocomplete({
+        delay: 200,
+        minLength: 0,
+        source: '[% c.uri_for('/json/refval', 'group', 'memberUID') %]',
+    }).autocomplete( "instance" )._renderItem = function( ul, item ) {
+        return $( "<li>" )
+        .append( "<a>" + item.label + " (" + item.value +  ")</a>" )
+        .appendTo( ul );
+    };
+});
+</script>
+
+[% ELSE %]
+[% perm_denied %]
+[% END %]
+</td>
+</tr>
+</table>
+
+</div>
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/adv_filter.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/adv_filter.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/adv_filter.tt	(revision 2119)
@@ -0,0 +1,23 @@
+<!-- $Id$ -->
+[% IF ofilter %]
+<p>Filtre:</p>
+<form action="[% c.action.namespace %]" method="POST">
+<input type="hidden" name="otype" value="[% otype | html %]">
+[% FOREACH item = ofilter.count_field %]
+[% IF loop.first %]
+<table border="0">
+<tr><th>Champs</th><th>contient:</th></tr>
+[% END %]
+<tr>
+<td>
+[% ofilter.search_field(item) %]
+</td>
+<td>[% ofilter.val_field(item) %]</td></tr>
+[% IF loop.last %]
+</table>
+[% END %]
+[% END %]
+
+<input type="submit">
+</form>
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/aliases_list.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/aliases_list.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/aliases_list.tt	(revision 2119)
@@ -0,0 +1,25 @@
+<!-- $Id$ -->
+
+[% aliases = c.model('Accounts').list_unowned_aliases(c.req.param('filter')) %]
+
+<div style="border: outset; padding: 0.5em; float:left;">
+[% FOREACH alias = aliases.keys.sort %]
+[% IF loop.first %]
+<table border=1>
+<tr><th>Nom</th><th>renvois</th></tr>
+[% END %]
+<tr>
+    <td>
+    <a href="[% c.uri_for('/aliases', alias) %]">[% alias | html %]</a>
+    </td>
+    <td>
+        [% aliases.$alias.join(',') | truncate(40) | html %]
+    </td>
+</tr>
+[% IF loop.last %]
+</table>
+[% END %]
+[% END %]
+</div>
+
+<div style="flush: both"></div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/defs.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/defs.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/defs.tt	(revision 2119)
@@ -0,0 +1,34 @@
+[% MACRO l(text, args) BLOCK;
+    c.localize(text, args);
+END; %]
+
+[% perm_denied = BLOCK %]
+<span class="permdenied"><img src="[% c.uri_for('/static', 'images', 'process-stop.png') %]">
+    permission non accordÃ©
+</span>
+[% END %]
+
+[% BLOCK user_flag %]
+[% INCLUDE object_flag object = ouser %]
+[% END %]
+
+[% BLOCK object_flag %]
+[% IF object.type == 'user' %]
+    [% IF object.get_attributes('unexported') %]
+<img src="[% c.uri_for('/static', 'images', 'gtk-delete.png') %]"
+class="notice" title="Ce compte est dÃ©sactivÃ©" alt="disable">
+    [% ELSIF object.get_attributes('expired') %]
+<img src="[% c.uri_for('/static', 'images', 'emblem-urgent.png') %]"
+class="notice" title="Ce compte est expirÃ© ([% object.get_attributes('expired') %])" alt="expired">
+    [% ELSIF object.get_attributes('expire') %]
+<img src="[% c.uri_for('/static', 'images', 'appointment-new.png') %]"
+class="notice" title="Ce compte est temporaire ([% object.get_attributes('expire') %])" alt="temporary">
+    [% END %]
+[% ELSE %]
+    [% IF object.get_attributes('unexported') %]
+<img src="[% c.uri_for('/static', 'images', 'gtk-delete.png') %]"
+class="notice" title="Cette entrÃ©e est dÃ©sactivÃ©s" alt="disable">
+    [% END %]
+[% END %]
+[% END %]
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/filter.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/filter.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/filter.tt	(revision 2119)
@@ -0,0 +1,9 @@
+<!-- $Id$ -->
+[% IF ofilter %]
+<form action="[% c.action.namespace %]" method="POST">
+Filtre:
+<input name=q value="[% c.req.param('q') | html %]" id="q"> 
+<input type="submit">
+</form>
+
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/form.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/form.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/form.tt	(revision 2119)
@@ -0,0 +1,34 @@
+<!-- $Id$ -->
+
+[% attributes = form.attributes() %]
+[% IF attributes.0 %]
+[% FOREACH attr =  attributes %]
+[% IF loop.first %]
+<h2>
+    [% form.label | html %]:
+</h2>
+<form action="[% form_uri | html %]" method="POST">
+<table border=1>
+[% END %]
+<tr>
+    <th>
+        [% form.attr_label(attr) %]
+        [% IF form.attr_hint(attr) %]
+        <span
+            title="[% form.attr_hint(attr) | html %]"
+            class="hint">?</span>
+        [% END %]
+    </th>
+    <td>[% form.attr_field(attr) %]</td>
+</tr>
+[% IF loop.last %]
+[% IF form.write_attributes %]
+<tr><td colspan=2>[% form.submit %]</td></tr>
+[% END %]
+</table>
+</form>
+[% END %]
+[% END %]
+[% ELSE %]
+[% perm_denied %]
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/list_objects.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/list_objects.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/list_objects.tt	(revision 2119)
@@ -0,0 +1,54 @@
+
+[% objectslist = ofilter.objectslist %]
+[% IF objectslist.size > 20 %]
+
+[% FOREACH initial = ofilter.initials %]
+[% IF c.req.param('start') == initial %]
+< [% initial %] >
+[% ELSE %]
+[% ofilter.uparams.start = initial %]
+<a href="[% c.uri_for('/' _ c.forward('/obj_to_uri', [ ofilter.otype ]), ofilter.uparams) %]">[% initial %]</a>
+[% END %]
+[% END %]
+
+[% END %]
+
+[% otype = ofilter.otype %]
+[% attrs = c.config.objects.$otype.list %]
+[% IF NOT attrs %][% attrs = [ 'cn' ] %][% END %]
+
+<table border=0>
+<tr>
+<td></td>
+<th>Nom</th>
+[% FOREACH attr = attrs %]
+<th>[% attr | html %]</th>
+[% END %]
+</tr>
+
+[% IF c.req.param('start') %]
+[% objectslist = ofilter.sublist %]
+[% END %]
+
+[% IF objectslist.size < 20 OR c.req.param('start') %]
+[% FOREACH name = objectslist %]
+[% object = c.model('Accounts').db.get_object(ofilter.otype, name) %]
+<tr>
+    <td>
+    [% INCLUDE object_flag object = object %]
+    </td>
+    <td><a href="[% c.uri_for('/' _ c.forward('/obj_to_uri', [ ofilter.otype ]), object.id) %]">
+        <img src="[% c.uri_for('/static', 'images', 'icon_edit.png') %]"
+            class="editobj"
+            alt="[% "edit " _ object.id | html %]"></a>
+    [% object.id | html %]</td>
+
+[% FOREACH attr = attrs %]
+<td>[% object.get_attributes(attr) | html %]</td>
+[% END %]
+</tr>
+[% END %]
+[% END %]
+
+</table>
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/object_header.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/object_header.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/object_header.tt	(revision 2119)
@@ -0,0 +1,44 @@
+<div id="oinfo">
+    <div id="oinfo_icons">
+    [% IF NOT headerobj.get_c_field('exported') %]
+    <img src="[% c.uri_for('/static', 'images', 'gtk-delete.png') %]"
+        alt="[% headerobj.type |ucfirst |html %] [% userobj.id | html %]"
+        title="[% l('the object is disabled') %]">
+    [% ELSE %]
+        [% SWITCH headerobj.type %]
+        [% CASE 'user' %]
+            [% img = c.uri_for('/static', 'images', 'user_conf.png') %]
+        [% CASE 'group' %]
+            [% img = c.uri_for('/static', 'images', 'group_conf.png') %]
+        [% CASE 'aliases' %]
+            [% img = c.uri_for('/static', 'images', 'mail-read.png') %]
+        [% CASE 'service' %]
+            [% img = c.uri_for('/static', 'images', 'service_conf.png') %]
+        [% CASE 'nethost' %]
+            [% img = c.uri_for('/static', 'images', 'preferences-desktop-wallpaper.png') %]
+        [% CASE 'netzone' %]
+            [% img = c.uri_for('/static', 'images', 'preferences-desktop-wallpaper.png') %]
+        [% CASE 'site' %]
+            [% img = c.uri_for('/static', 'images', 'site_conf.png') %]
+        [% CASE %]
+            [% img = c.uri_for('/static', 'images', 'service_conf.png') %]
+        [% END %]
+            <img src="[% img %]" alt="[% headerobj.type |ucfirst |html %] [% userobj.id | html %]">
+    [% END %]
+
+    [% IF headerobj.type == 'user' AND headerobj.get_c_field('locked') %]
+        <img src="[% c.uri_for('/static', 'images', 'locked.png') %]"
+            alt="le compte est vÃ©rrouillÃ©" title="le compte est vÃ©rrouillÃ©">
+    [% END %]
+    </div>
+
+    <div id="oinfo_label">
+    [% IF headerobj.get_attributes('oalias') %]
+        [% objclass = [ 'oalias' ] %]
+    [% END %]
+    <p id="oname" class="[% objclass.join(' ') %]">[% headerobj.id | html %]
+        [% IF headerobj.type == 'user' %]
+        [% INCLUDE user_flag ouser = headerobj %][% END %]</p>
+    <p class="[% objclass.join(' ') %]">[% headerobj.get_attributes('description') || headerobj | html %]</p>
+    </div>
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/select_objects_list.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/select_objects_list.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/select_objects_list.tt	(revision 2119)
@@ -0,0 +1,24 @@
+[% objectslist = ofilter.objectslist %]
+<select name="obj" id="obj">
+[% IF objectslist.size > 1 %]
+<option value="">--</option>
+[% END %]
+
+[% FOREACH name = objectslist %]
+
+[% IF objectslist.size < 20 %]
+[% object = c.model('Accounts').db.get_object(ofilter.otype, name) %]
+<option value="[% name | html %]">[% name | html %]
+[% disp = object.get_attributes('displayName') %]
+[% IF disp %]
+([%disp | html %])
+[% END %]
+</option>
+[% ELSE %]
+<option value="[% name | html %]">[% name | html %]</option>
+[% END %]
+
+[% END %]
+
+</select>
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/sidebar.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/sidebar.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/sidebar.tt	(revision 2119)
@@ -0,0 +1,115 @@
+[% IF c.user.username %]
+<form action="[% c.uri_for('/search') %]">
+<input name="q" value="[% c.req.param('q') | html %]">
+<input type="image" name="" src="[% c.uri_for('/static', 'images', 'gtk-find.png') %]" height="16" width="16">
+</form>
+<span>
+    <a href="[% c.uri_for('/users', c.user.username) %]">
+    [% l('Connected as') %]
+    [% c.model('Accounts').db.get_object('user', c.user.username ) %]
+    </a>
+</span>,
+[% IF c.model('Accounts').db.check_acl('user', 'userPassword', 'w') %]
+    <span>
+    <a href="[% c.uri_for('/users', c.user.username, 'passwd') %]">
+    [% l('Change my password') %]
+    </a>
+    </span>,
+[% END %]
+<span>
+    <a href="[% c.uri_for('/logout') %]">[% l('Logout') %]</a>
+</span>
+
+<ul id="menu">
+
+<li><a href="[% c.uri_for('/search', 'adv') %]">Recherche avancÃ©e</a></li>
+
+<li>[% l('View') %]
+<ul>
+[% FOREACH obj = c.model('Accounts').db.list_supported_objects.sort %]
+[% label = c.model('Accounts').obj_to_label(obj) %]
+[% NEXT IF NOT label %]
+    <li><a href="[% c.model('Accounts').obj_to_uri(obj) %]">[% label | html %]</a></li>
+[% END %]
+    <li><a href="[% c.uri_for('/oaliases') %]">Alias d'objet</a></li>
+</ul>
+</li>
+
+[% IF c.model('Accounts').CanCreateObjects %]
+<li>[% l('Create') %]
+<ul>
+[% FOREACH obj = c.model('Accounts').db.list_supported_objects.sort %]
+[% label = c.model('Accounts').obj_to_label(obj) %]
+[% NEXT IF NOT label %]
+    <li><a href="[% c.uri_for('/create', obj) %]">
+    [% label | html %]
+    </a></li>
+[% END %]
+    <li><a href="[% c.uri_for('/oaliases', 'create') %]">
+        Alias d'objet
+    </a></li>
+    <li><a href="[% c.uri_for('/tools/loadnew') %]">
+        [% l('Using formated file') %]
+    </a></li>
+    <li><a href="[% c.uri_for('/tools/csv') %]">
+        [% l('Mass from CSV...') %]
+    </a></li>
+</ul>
+</li>
+[% END # IF CanCreateObjects %]
+
+<li>[% l('Statistics') %]
+    <ul>
+    [% IF c.config.features.offices_list %]
+    <li><a href="[% c.uri_for('/summary', 'offices') %]">Annuaire par bureaux</a></li>
+    [% END %]
+
+    <li><a href="[% c.uri_for('/summary', 'departments') %]">Liste des dÃ©partements</a></li>
+    <li><a href="[% c.uri_for('/summary/expire') %]">Expiration des comptes</a></li>
+    <li><a href="[% c.uri_for('/summary/employment') %]">[% l('Status evolution') %]</a></li>
+    <li><a href="[% c.uri_for('/summary/accage') %]">Comptes par annÃ©e</a></li>
+    <li><a href="[% c.uri_for('/summary/employmentbyperiod') %]">Nombre de personnes par statut</a></li>
+
+    [% FOREACH stat = c.model('Accounts').db.search_objects('stat', 'exported=true', 'display=*').sort %]
+    [% ostat = c.model('Accounts').db.get_object('stat', stat) %]
+    [% IF loop.first %]
+    <li class="ui-widget-header"><a href="[% c.uri_for('/stat') %]">[% l('Statistics') %]</a>:
+    [% END %]
+    <li><a href="[% c.uri_for('/stat', stat) %]">[% ostat.get_attributes('description') || stat | html %]</a>
+    </li>
+
+    [% IF loop.last %]
+    </li>
+    [% END %]
+    [% END %]
+    
+    [% IF c.model('Accounts').db.search_objects('stat', 'exported=true', 'display=*').sort %]
+    <li><a href="[% c.uri_for('/stat') %]">[% l('Others Statistics') %]</a>
+    </li>
+    [% END %]
+    </ul>
+</li>
+
+<li>[% l('Tools') %]
+    <ul>
+    [% IF c.model('Accounts').db.check_acl('user', 'userPasswd', 'w') %]
+    <li><a href="[% c.uri_for('/tools', 'loadpasswd') %]">
+        [% l('Load Password') %]
+    </a></li>
+    [% END %]
+    </ul>
+</li>
+
+<li><a href="[% c.uri_for('/about') %]">
+    [% l('About') %]</a></li>
+</ul>
+
+<script>
+$(function() {
+    $( "#menu" ).menu({
+        position: { my: "left top", at: "left-1 top+35" },
+        items: "> :not(.ui-widget-header)",
+    });
+}); 
+</script>
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/wrapper.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/wrapper.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/includes/wrapper.tt	(revision 2119)
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>[% IF c.config.company %][% c.config.company _ ' ' | html %][% END %]Accounts / [% page.title %]</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<link rel="stylesheet" href="[% c.uri_for('/static', 'style.css') %]?" type="text/css">
+<link rel="stylesheet" href="[% c.uri_for('/static', 'struct.css') %]?" type="text/css">
+<link rel="icon" href="[%- c.uri_for('/static', 'images', 'favicon.ico') -%]" type="image/x-icon">
+
+<script src="[% c.uri_for('/static', 'js', 'jquery', 'external/jquery/jquery.js') %]"></script>
+
+<script src="[% c.uri_for('/static', 'js', 'jquery', 'jquery-ui.js') %]"></script>
+<link rel="stylesheet" href="[% c.uri_for('/static/js/jquery/jquery-ui.css') %]" type="text/css">
+
+<script src="[% c.uri_for('/static', 'js', 'jqplot', 'jquery.jqplot.js') %]"></script>
+[% FOREACH plugins = [
+    'jqplot.json2.js',
+    'jqplot.pieRenderer.js',
+    'jqplot.dateAxisRenderer.js',
+    'jqplot.barRenderer.js',
+    'jqplot.cursor.js'
+    'jqplot.highlighter.js',
+    'jqplot.enhancedLegendRenderer.js',
+    'jqplot.canvasTextRenderer.js',
+    'jqplot.canvasAxisTickRenderer.js',
+    'jqplot.categoryAxisRenderer.js',
+    'jqplot.canvasAxisTickRenderer.js',
+    ] %]
+<script src="[% c.uri_for('/static', 'js', 'jqplot', 'plugins', plugins) %]"></script>
+[%- END %]
+
+<script src="[% c.uri_for('/static', 'js', 'd3', 'd3.v3.js') %]"></script>
+<script src="[% c.uri_for('/static', 'js', 'timeline', 'timeframe.js') %]"></script>
+<script src="[% c.uri_for('/static', 'js', 'jquery', 'jquery.orgchart.js') %]"></script>
+<link rel="stylesheet" href="[% c.uri_for('/static/js/jquery/jquery.orgchart.css') %]" type="text/css">
+
+<link rel="stylesheet" href="[% c.uri_for('/static/js/jqplot/jquery.jqplot.css') %]" type="text/css">
+
+<script>
+$.jqplot.config.enablePlugins = true;
+</script>
+
+[% IF page.error %]
+<script type="text/javascript" language="JavaScript">
+window.onload = analert;
+function analert () {
+    alert('[% page.error | replace('\'', '\\\'') %]');
+}
+</script>
+[% END %]
+</head>
+
+<body>
+<!-- $Id$ -->
+
+<div id="header">
+
+<h1 style="">
+    [% l('IT management') %]
+    [% IF c.config.company %]
+    - [% c.config.company | html %]
+    [% END %]
+</h1>
+
+</div><!-- #header -->
+
+[% IF c.user.username %]
+<div id="sidebar">
+    [% INCLUDE 'includes/sidebar.tt' %]
+</div>
+[% END %]
+
+<div id="mainbox">
+
+[% content %]
+
+</div><!-- #mainbox -->
+
+<div id="footer" style="clear: both"></div>
+</body>
+</html>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/index.tt	(revision 2119)
@@ -0,0 +1,1 @@
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/login/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/login/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/login/index.tt	(revision 2119)
@@ -0,0 +1,25 @@
+<div id="login" style="margin-left: auto; margin-right: auto; width: 30em">
+<form action="[% c.uri_for('/login') %]" method="POST">
+<table border="0" align="center">
+<tr>
+<tr><td colspan=2>Vous devez vous connecter:</td></tr>
+<th>Login:</th><td><input type="text" name="username" value="[% c.req.param('username') | html %]"></td>
+</tr>
+<tr>
+<th>Password:</th><td><input type="password" name="password"></td>
+</tr>
+<tr><td></td><td><input type="submit"></td></tr>
+</table>
+</form>
+
+[% IF sslid AND sslid != 'NULL' %]
+<form action="[% c.uri_for('/login') %]" method="POST">
+<p style="text-align: center;">Ou s'authentifier via SSL en tant que: [% sslid | html %]
+<br>
+<input type=hidden name=use_ssl value=1>
+<input type=submit name=username
+    value="Valider">
+</p>
+</form>
+[% END %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/logout/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/logout/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/logout/index.tt	(revision 2119)
@@ -0,0 +1,3 @@
+<!-- $Id$ -->
+
+<p><a href="[% c.uri_for('/') %]">Se reconnecter...</a></p>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/logs/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/logs/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/logs/index.tt	(revision 2119)
@@ -0,0 +1,98 @@
+<form action="[% c.uri_for('/logs') %]">
+<label for=otype>Type d'objet</label> 
+<select id=otype name=otype>
+<option value="">--</option>
+[% FOREACH otype = c.model('Accounts').db.list_supported_objects %]
+<option value=[% otype %][% " selected" IF otype == c.req.param('otype') %]>[% otype | html %]</option>
+[% END %]
+</select>
+<br>
+<label for=object>Nom de l'object:</label><input id=object name=object value="[% c.req.param('object') %]">
+<br>
+<label for=modifier>ModifiÃ© par:</label><input id=modifier name=modifier value="[% c.req.param('modifier') %]">
+<br>
+<input type=submit>
+</form>
+
+<script>
+$(function () {
+    $('#object').autocomplete({
+        minLength: 0,
+        source: function( request, response ) {
+            $.ajax({
+                url: '[% c.uri_for("/json/list") %]',
+                data: {
+                    otype: $('#otype').val(),
+                    term: request.term,
+                },
+                success: function( data ) {
+                    response( data );
+                }
+            });
+        },
+    });
+    $('#modifier').autocomplete({
+        minLength: 0,
+        source: function( request, response ) {
+            $.ajax({
+                url: '[% c.uri_for("/json/list") %]',
+                data: {
+                    otype: 'user',
+                    term: request.term,
+                },
+                success: function( data ) {
+                    response( data );
+                }
+            });
+        },
+    });
+
+});
+</script>
+
+[% IF logobject %]
+<p>[% logobject.type %] / [% logobject.id %] <a href="[% c.uri_for('/logs', {
+    'okey' => logobject.get_attributes('ikey'),
+    'modifier' => c.req.param('modifier') } ) %]">Traverser les renomages</a></p>
+[% END %]
+
+[% FOREACH logentry = logs %]
+[% IF loop.first %]
+<table border=1 style="font-size: 12px">
+[% END %]
+
+<tr>
+<td>
+[% logentry.logdate |remove('\..*') %]<br>
+</td>
+
+[% IF NOT c.req.param('modifier') %]
+<td>
+[% logentry.username | html %]
+</td>
+[% END %]
+
+[% IF NOT logobject %]
+<td>
+[% logentry.otype | html %] /
+[% logentry.name | html %]
+</td>
+[% END %]
+
+<td>
+<a href="[% c.uri_for('/logs', {
+    'okey' => logentry.ikey,
+    'modifier' => c.req.param('modifier') } ) %]">[% logentry.ikey %]</a>
+</td>
+
+<td>
+<b>[% logentry.changetype | html %]</b>:
+[% logentry.message | html %]
+</td>
+
+</tr>
+
+[% IF loop.last %]
+</table>
+[% END %]
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/nethosts/default.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/nethosts/default.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/nethosts/default.tt	(revision 2119)
@@ -0,0 +1,76 @@
+<!-- $Id$ -->
+[% IF NOT nethost %]
+No host [% nethostname | html %] found.
+[% ELSE %]
+    
+[% INCLUDE 'nethosts/object_header.tt' %]
+
+<div class="objectformleft" id="objectform">
+[% INCLUDE 'includes/form.tt'
+    form = form
+    form_uri = c.uri_for(nethostname) %]
+
+<table border=1>
+[% IF c.model('Accounts').db.check_acl('netzone', 'name', 'r') %]
+<tr><th>Zone de cette hÃŽte</th></tr>
+<tr><td>
+[% FOREACH z = nethost.get_attributes('netZone').sort %]
+<a href="[% c.uri_for('/netzones', z) %]">
+[% z %]</a> ([% c.model('Accounts').db.get_object('netzone', z).get_attributes('type') %])
+<br>
+[% END %]
+</td></tr>
+[% END %]
+
+<tr><td>
+[% IF c.model('Accounts').db.check_acl('nethost', 'ip', 'w') %]
+<fieldset>
+<legend>Ajouter une ip libre:</legend>
+<form action="[% c.uri_for(nethostname) %]" method="POST">
+<label  for="zone">Zone:</label> <select name="zone" id="zone">
+<option value="">--</value>
+[% FOREACH z =
+   c.model('Accounts').db.search_objects('netzone', 'type=dhcp', 'net=*').sort %]
+[% zo = c.model('Accounts').db.get_object('netzone', z) %]
+[% NEXT IF NOT zo.get_attributes('freeIPCount') %]
+<option value="[% z | html %]">[% z | html %] ([% zo.get_attributes('freeIPCount') %])</option>
+[% END %]
+</select><br>
+
+<input id=ipselect type=text name=newip><br>
+<input type="submit" name="addip">
+</form>
+</fieldset>
+
+<script>
+$(function() {
+    $( '#zone' ).on( "change", function() {
+        $( '#ipselect' ).autocomplete({
+            minLength: 0,
+            delay: 0,
+            source: '[% c.uri_for('/json', 'freeip') %]?zone=' + $( '#zone' ).val(),
+        });
+    $( "#ipselect" ).focus(function() { $( "#ipselect" ).autocomplete("search", "" ); });
+    $( "#ipselect" ).click(function() { $( "#ipselect" ).autocomplete("search", "" ); });
+        return false;
+    });
+});
+</script>
+[% END %]
+
+[% IF c.model('Accounts').db.check_acl('nethost', '@CREATE', 'w') %]
+<fieldset><legend>Renommer la machine en:</legend>
+<form action="[% c.uri_for(nethostname) %]" method="POST">
+<input type="text" name="to"><br>
+<input type="checkbox" name="addcname" id="addcname" checked="checked">
+<label for="addcname">Ajouter le nom en CNAME</label><br>
+<input type="submit" name="rename">
+</form>
+</fieldset>
+[% END %]
+
+</td></tr>
+</table>
+</div>
+
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/nethosts/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/nethosts/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/nethosts/index.tt	(revision 2119)
@@ -0,0 +1,10 @@
+<!-- $Id$ -->
+<div id="filter">
+[% INCLUDE 'includes/filter.tt'
+   formuri = c.uri_for()
+%]
+</div>
+
+<div id="list_objects">
+[% INCLUDE 'includes/list_objects.tt' %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/nethosts/menu.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/nethosts/menu.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/nethosts/menu.tt	(revision 2119)
@@ -0,0 +1,2 @@
+<!-- $Id$ -->
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/nethosts/object_header.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/nethosts/object_header.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/nethosts/object_header.tt	(revision 2119)
@@ -0,0 +1,33 @@
+<!-- $Id$ -->
+
+[% INCLUDE 'includes/object_header.tt'
+    headerobj = nethost
+    %]
+
+<div id="oaction">
+[% IF c.model('Accounts').db.check_acl(nethost, 'exported', 'w') %]
+    [% IF NOT nethost.get_c_field('exported') %]
+    <form method="POST" action="[% c.uri_for('/nethosts', nethostname, subform) %]"
+          onSubmit="return confirm('Activer l\'hÃŽte [% nethostname %] ?') ? true : false">
+    <input type="image" name="make_active" value="Activer" title="Activer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-enable.png') %]">
+    <input type="hidden" value="activer" name="make_active">
+    </form>
+    [% ELSE %]
+    <form method="POST" action="[% c.uri_for('/nethosts', nethostname, subform) %]"
+          onSubmit="return confirm('DÃ©sactiver l\'hÃŽte [% nethostname %] ?') ? true : false">
+    <input type="image" name="make_inactive" value="DÃ©sactiver" title="DÃ©sactiver"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-disable.png') %]">
+    <input type="hidden" value="dÃ©sactiver" name="make_inactive">
+    </form>
+    [% END %]
+[% END %]
+[% IF c.model('Accounts').db.check_acl(nethost, 'DELETE', 'w') %]
+    <form method="POST" action="[% c.uri_for('/nethosts', nethostname, subform) %]"
+          onSubmit="return confirm('DÃ©truire l\'hÃŽte [% nethostname %] ?') ? true : false">
+    <input type="image" name="delete" value="Supprimer" title="Supprimer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-delete.png') %]">
+    <input type="hidden" value="DETRUIRE" name="delete">
+    </form>
+[% END %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/netzones/default.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/netzones/default.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/netzones/default.tt	(revision 2119)
@@ -0,0 +1,49 @@
+<!-- $Id$ -->
+[% IF NOT netzone %]
+No group [% netzonename | html %] found.
+[% ELSE %]
+    
+[% INCLUDE 'netzones/object_header.tt' %]
+
+<div class="objectformleft" id="objectform">
+[% INCLUDE 'includes/form.tt'
+    form = form
+    form_uri = c.uri_for(netzonename) %]
+
+<table border=1>
+[% FOREACH host = netzone.get_attributes('hostsExclude').sort %]
+[% IF loop.first %]
+<tr>
+<th>Hosts excluded</th>
+</tr>
+<tr>
+<td>
+[% END %]
+[% ohost = c.model('Accounts').db.get_object('nethost', host) %]
+<a href="[% c.uri_for('/nethosts', host) %]">[% host | html %]</a><br>
+[% INCLUDE object_flag object = ohost %]
+[% IF loop.last %]
+</td>
+</tr>
+[% END %]
+[% END %]
+[% FOREACH host = netzone.get_attributes('hosts').sort %]
+[% IF loop.first %]
+<tr>
+<th>Hosts</th>
+</tr>
+<tr>
+<td>
+[% END %]
+[% ohost = c.model('Accounts').db.get_object('nethost', host) %]
+<a href="[% c.uri_for('/nethosts', host) %]">[% host | html %]</a>
+[% INCLUDE object_flag object = ohost %]
+<br>
+[% IF loop.last %]
+</td></tr>
+[% END %]
+[% END %]
+</table>
+</div>
+
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/netzones/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/netzones/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/netzones/index.tt	(revision 2119)
@@ -0,0 +1,10 @@
+<!-- $Id$ -->
+<div id="filter">
+[% INCLUDE 'includes/filter.tt'
+   formuri = c.uri_for()
+%]
+</div>
+
+<div id="list_objects">
+[% INCLUDE 'includes/list_objects.tt' %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/netzones/menu.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/netzones/menu.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/netzones/menu.tt	(revision 2119)
@@ -0,0 +1,14 @@
+<!-- $Id$ -->
+
+<div id="oinfo">
+    <div id="oinfo_icons">
+    <img src="[% c.uri_for('/static', 'images', 'preferences-desktop-wallpaper.png') %]"
+    alt="[% "Ordinateur " _ hostgroupname %]">
+    </div>
+    <div id="oinfo_label">
+    <p id="oname">[% netzonename | html %]</p>
+    <p>[% netzone.get_c_field('type') | html %]</p>
+    <p>[% netzone.get_c_field('description') | html %]</p>
+    </div>
+</div>
+<div 
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/netzones/object_header.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/netzones/object_header.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/netzones/object_header.tt	(revision 2119)
@@ -0,0 +1,33 @@
+<!-- $Id$ -->
+
+[% INCLUDE 'includes/object_header.tt'
+    headerobj = netzone
+    %]
+
+<div id="oaction">
+[% IF c.model('Accounts').db.check_acl(netzone, 'exported', 'w') %]
+    [% IF NOT netzone.get_c_field('exported') %]
+    <form method="POST" action="[% c.uri_for('/netzones', netzonename, subform) %]"
+          onSubmit="return confirm('Activer la zone [% netzonename %] ?') ? true : false">
+    <input type="image" name="make_active" value="Activer" title="Activer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-enable.png') %]">
+    <input type="hidden" value="activer" name="make_active">
+    </form>
+    [% ELSE %]
+    <form method="POST" action="[% c.uri_for('/netzones', netzonename, subform) %]"
+          onSubmit="return confirm('DÃ©sactiver la zone [% netzonename %] ?') ? true : false">
+    <input type="image" name="make_inactive" value="DÃ©sactiver" title="DÃ©sactiver"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-disable.png') %]">
+    <input type="hidden" value="dÃ©sactiver" name="make_inactive">
+    </form>
+    [% END %]
+[% END %]
+[% IF c.model('Accounts').db.check_acl(netzone, 'DELETE', 'w') %]
+    <form method="POST" action="[% c.uri_for('/netzones', netzonename, subform) %]"
+          onSubmit="return confirm('DÃ©truire la zone [% netzonename %] ?') ? true : false">
+    <input type="image" name="delete" value="Supprimer" title="Supprimer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-delete.png') %]">
+    <input type="hidden" value="DETRUIRE" name="delete">
+    </form>
+[% END %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/no_object.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/no_object.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/no_object.tt	(revision 2119)
@@ -0,0 +1,3 @@
+<div id="objectform">
+<p>Impossible de trouver l'objet demandÃ©</p>
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/oaliases/Create.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/oaliases/Create.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/oaliases/Create.tt	(revision 2119)
@@ -0,0 +1,40 @@
+<div style="display: inline-block; vertical-align: top">
+<h3>CrÃ©er un alias d'object:</h3>
+<form action="[% c.uri_for('/oaliases', 'create') %]">
+Type d'oject:
+<select name=otype id=otype>
+<option value=user>Utilisateur:</option>
+</select>
+<br>
+Nom de l'objet:
+<input type=text id=name   name=name>
+<br>
+RÃ©fÃ©rences:
+<input type=text id=oalias name=oalias>
+<br>
+<input type=submit>
+<script>
+$(function() {
+    $('#oalias').autocomplete({
+        delay: 200,
+        minLength: 0,
+        source: function( request, response ) {
+            $.ajax({
+                url: '[% c.uri_for('/json/list') %]',
+                data: {
+                    otype: $('#otype').val(),
+                    term: request.term,
+                },
+                success: function(data) {
+                    response(data);
+                },
+            });
+        },
+    });
+    $( "#oalias" ).focus(function() { $( "#oalias" ).autocomplete("search", "" ); });
+    $( "#oalias" ).click(function() { $( "#oalias" ).autocomplete("search", "" ); });
+});
+</script>
+
+</form>
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/oaliases/Edit.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/oaliases/Edit.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/oaliases/Edit.tt	(revision 2119)
@@ -0,0 +1,46 @@
+<div style="display: inline-block; width 60%; vertical-align: top">
+[% INCLUDE 'oaliases/index.tt' %]
+</div>
+<div style="display: inline-block; vertical-align: top">
+<h3>Alias [% objectalias.type %]/[% objectalias.id | html %]</h3>
+<form action="[% c.uri_for('/oaliases', objectalias.type, objectalias.id) %]">
+<label for=oalias>Alias pour</label>
+<input type=text id=oalias name=oalias value="[% objectalias.get_attributes('oalias') | html %]">
+<br>
+<label for=exported>ExportÃ©:</label>
+<input name=exported id=exported type=checkbox value=1 [% 'checked' IF objectalias.get_attributes('exported') %]>
+<br>
+<input type=submit>
+<input type=submit name=delete value="DÃ©truire" id=delete>
+<script>
+$(function() {
+    $('#oalias').autocomplete({
+        delay: 200,
+        minLength: 0,
+        source: function( request, response ) {
+            $.ajax({
+                url: '[% c.uri_for('/json/list') %]',
+                data: {
+                    otype: $('#otype').val(),
+                    term: request.term,
+                },
+                success: function(data) {
+                    response(data);
+                },
+            });
+        },
+    });
+    $( "#oalias" ).focus(function() { $( "#oalias" ).autocomplete("search", "" ); });
+    $( "#oalias" ).click(function() { $( "#oalias" ).autocomplete("search", "" ); });
+
+    $( '#delete' ).on('click', function(event) {
+        if (!confirm("DÃ©truire l'alias [% objectalias.type %]/[% objectalias.id | html %] ?")) {
+            event.preventDefault();
+            return;
+        }
+    });
+});
+</script>
+</form>
+
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/oaliases/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/oaliases/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/oaliases/index.tt	(revision 2119)
@@ -0,0 +1,27 @@
+[% FOREACH otype = c.model('Accounts').db.list_supported_objects %]
+
+[% FOREACH object = c.model('Accounts').db.search_objects(otype, 'oalias=*') %]
+[% aobject = c.model('Accounts').db.GetAlias(otype, object) %]
+[% oobject = c.model('Accounts').db.get_object(otype, object) %]
+[% IF loop.first %]
+<h3>[% otype %]</h3>
+<ul>
+[% END %]
+
+<li>
+[% IF aobject.get_attributes('unexported') %]
+<img src="[% c.uri_for('/static', 'images', 'gtk-delete.png') %]"
+class="notice" title="Cet alias est dÃ©sactivÃ©" alt="disable">
+[% END %]
+<a href=[% c.uri_for('/oaliases', otype, object) %]>[% object | html %]</a>
+&#10137;
+[% aobject.get_attributes('oalias') | html %]
+([% INCLUDE object_flag object = oobject %][% oobject | html %] )
+</li>
+
+[% IF loop.last %]
+</ul>
+[% END %]
+[% END %]
+
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/search/adv/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/search/adv/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/search/adv/index.tt	(revision 2119)
@@ -0,0 +1,56 @@
+[% IF NOT c.session.searchOtype %]
+
+[% otype = 'user' %] [% # Default value %]
+<form action="[% c.uri_for('/search', 'adv') %]">
+<select name=otype id=otype>
+[% FOREACH totype = ['user', 'group', 'nethost', 'aliases' ] %]
+<option value="[% totype %]">[% totype %]</option>
+[% END %]
+</select>
+<input type=submit>
+</form>
+
+[% ELSE %]
+
+
+<div id="search-form">
+<p>Rechercher des object de type: [% c.forward('/obj_to_label', [
+c.session.searchOtype ]) |html %]</p>
+<div class="bloc-box">
+<p>Ajouter un filtre:</p>
+<form action="[% c.uri_for('/search', 'adv') %]">
+<select name=attr>
+[% FOREACH attr = c.stash.db.list_canonical_fields(c.session.searchOtype) %]
+<option name="[% attr |html %]">[% attr | html %]</option>
+[% END %]
+</select>
+contient: <input type="text" name="attrval">
+<br>
+<input type=submit value="Ajouter">
+</form>
+</div>
+
+<div class="bloc-box">
+<p>Filtre actuels:</p>
+<form action="[% c.uri_for('/search', 'adv') %]">
+<select multiple id="search-adv-filter" name=delval>
+[% FOREACH v = c.session.searchValues %]
+<option value="[% v | html %]">[% v | html %]</option>
+[% END %]
+</select>
+<input type=submit value="Supprimer la selection...">
+</form>
+</div>
+</div>
+
+[% END %]
+
+<div>
+[% FOREACH res = c.stash.objs %]
+[% "<ul>\n" IF loop.first %]
+[% obj = c.stash.db.get_object(c.session.searchOtype, res ) %]
+<li><a href="[% c.uri_for('/', c.forward('/obj_to_uri', [ c.session.searchOtype ]), res) %]">[% obj %]</a></li>
+[% "</ul>\n" IF loop.last %]
+[% END %]
+</div>
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/search/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/search/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/search/index.tt	(revision 2119)
@@ -0,0 +1,37 @@
+[% FOREACH otype = [ 'user', 'group', 'nethost', 'aliases', 'service' ] %]
+
+[% filter = c.model('AttrFilter', otype) %]
+
+[% attrs = c.config.objects.$otype.list %]
+[% IF NOT attrs %][% attrs = [ 'cn' ] %][% END %]
+
+[% IF filter.objectslist.size > 0 %]
+[% IF filter.objectslist.size > 50 %]
+<p>[% filter.objectslist.size %] [% otype %]...</p>
+[% ELSE %]
+
+<h3>[% c.forward('/obj_to_label', [ otype ]) | html %] ([% filter.objectslist.size %])</h3>
+<table>
+[% FOREACH name = filter.objectslist %]
+[% object = c.model('Accounts').db.get_object(filter.otype, name) %]
+<tr>
+    <td>
+    [% INCLUDE object_flag object = object %]
+    </td>
+    <td><a href="[% c.uri_for('/' _ c.forward('/obj_to_uri', [ filter.otype ]), object.id) %]">
+        <img src="[% c.uri_for('/static', 'images', 'icon_edit.png') %]"
+            height="16" width="16"
+            alt="[% "edit " _ object.id | html %]"></a>
+    [% object.id | html %]</td>
+
+[% FOREACH attr = attrs %]
+<td>[% object.get_attributes(attr) | html %]</td>
+[% END %]
+</tr>
+[% END %]
+</table>
+
+[% END %]
+[% END %]
+
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/services/default.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/services/default.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/services/default.tt	(revision 2119)
@@ -0,0 +1,14 @@
+<!-- $Id$ -->
+[% IF NOT service %]
+No serivce [% servicename | html %] found.
+[% ELSE %]
+    
+[% INCLUDE 'services/object_header.tt' %]
+
+<div class="objectform" id="objectform">
+
+[% INCLUDE 'includes/form.tt'
+    form = form
+    form_uri = c.uri_for(servicename, subform) %]
+</div>
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/services/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/services/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/services/index.tt	(revision 2119)
@@ -0,0 +1,10 @@
+<!-- $Id$ -->
+<div id="filter">
+[% INCLUDE 'includes/filter.tt'
+   formuri = c.uri_for()
+%]
+</div>
+
+<div id="list_objects">
+[% INCLUDE 'includes/list_objects.tt' %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/services/menu.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/services/menu.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/services/menu.tt	(revision 2119)
@@ -0,0 +1,13 @@
+<!-- $Id$ -->
+
+<div id="oinfo">
+    <div id="oinfo_icons">
+    <img src="[% c.uri_for('/static', 'images', 'user_conf.png') %]"
+    alt="[% "User " _ servicename %]">
+    </div>
+    <div id="oinfo_label">
+    <p id="oname">[% servicename | html %]</p>
+    <p>[% user.get_c_field('l') | html %]</p>
+    </div>
+</div>
+<div 
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/services/object_header.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/services/object_header.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/services/object_header.tt	(revision 2119)
@@ -0,0 +1,33 @@
+<!-- $Id$ -->
+
+[% INCLUDE 'includes/object_header.tt'
+    headerobj = service
+    %]
+
+<div id="oaction">
+[% IF c.model('Accounts').db.check_acl(service, 'exported', 'w') %]
+    [% IF NOT service.get_c_field('exported') %]
+    <form method="POST" action="[% c.uri_for('/services', servicename, subform) %]"
+          onSubmit="return confirm('Activer le service [% servicename %] ?') ? true : false">
+    <input type="image" name="img" value="Supprimer" title="Supprimer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-enable.png') %]">
+    <input type="hidden" value="activer" name="make_active">
+    </form>
+    [% ELSE %]
+    <form method="POST" action="[% c.uri_for('/services', servicename, subform) %]"
+          onSubmit="return confirm('DÃ©sactiver l\'utilisateur [% servicename %] ?') ? true : false">
+    <input type="image" name="delete" value="Supprimer" title="Supprimer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-disable.png') %]">
+    <input type="hidden" value="dÃ©sactiver" name="make_inactive">
+    </form>
+    [% END %]
+[% END %]
+[% IF c.model('Accounts').db.check_acl(service, 'DELETE', 'w') %]
+    <form method="POST" action="[% c.uri_for('/services', servicename, subform) %]"
+          onSubmit="return confirm('DÃ©truire le service [% servicename %] ?') ? true : false">
+    <input type="image" name="delete" value="Supprimer" title="Supprimer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-delete.png') %]">
+    <input type="hidden" value="DETRUIRE" name="delete">
+    </form>
+[% END %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/sites/default.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/sites/default.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/sites/default.tt	(revision 2119)
@@ -0,0 +1,14 @@
+<!-- $Id$ -->
+[% IF NOT site %]
+No site [% sitename | html %] found.
+[% ELSE %]
+    
+[% INCLUDE 'sites/object_header.tt' %]
+
+<div class="objectform" id="objectform">
+
+[% INCLUDE 'includes/form.tt'
+    form = form
+    form_uri = c.uri_for(sitename, subform) %]
+</div>
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/sites/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/sites/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/sites/index.tt	(revision 2119)
@@ -0,0 +1,10 @@
+<!-- $Id$ -->
+<div id="filter">
+[% INCLUDE 'includes/filter.tt'
+   formuri = c.uri_for()
+%]
+</div>
+
+<div id="list_objects">
+[% INCLUDE 'includes/list_objects.tt' %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/sites/menu.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/sites/menu.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/sites/menu.tt	(revision 2119)
@@ -0,0 +1,13 @@
+<!-- $Id$ -->
+
+<div id="oinfo">
+    <div id="oinfo_icons">
+    <img src="[% c.uri_for('/static', 'images', 'user_conf.png') %]"
+    alt="[% "User " _ sitename %]">
+    </div>
+    <div id="oinfo_label">
+    <p id="oname">[% sitename | html %]</p>
+    <p>[% user.get_c_field('l') | html %]</p>
+    </div>
+</div>
+<div 
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/sites/object_header.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/sites/object_header.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/sites/object_header.tt	(revision 2119)
@@ -0,0 +1,33 @@
+<!-- $Id$ -->
+
+[% INCLUDE 'includes/object_header.tt'
+    headerobj = user
+    %]
+
+<div id="oaction">
+[% IF c.model('Accounts').db.check_acl(site, 'exported', 'w') %]
+    [% IF NOT site.get_c_field('exported') %]
+    <form method="POST" action="[% c.uri_for('/sites', sitename, subform) %]"
+          onSubmit="return confirm('Activer le site [% sitename %] ?') ? true : false">
+    <input type="image" name="img" value="Supprimer" title="Supprimer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-enable.png') %]">
+    <input type="hidden" value="activer" name="make_active">
+    </form>
+    [% ELSE %]
+    <form method="POST" action="[% c.uri_for('/sites', sitename, subform) %]"
+          onSubmit="return confirm('DÃ©sactiver l\'utilisateur [% sitename %] ?') ? true : false">
+    <input type="image" name="delete" value="Supprimer" title="Supprimer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-disable.png') %]">
+    <input type="hidden" value="dÃ©sactiver" name="make_inactive">
+    </form>
+    [% END %]
+[% END %]
+[% IF c.model('Accounts').db.check_acl(site, 'DELETE', 'w') %]
+    <form method="POST" action="[% c.uri_for('/sites', sitename, subform) %]"
+          onSubmit="return confirm('DÃ©truire l\'utilisateur [% sitename %] ?') ? true : false">
+    <input type="image" name="delete" value="Supprimer" title="Supprimer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-delete.png') %]">
+    <input type="hidden" value="DETRUIRE" name="delete">
+    </form>
+[% END %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/stat/display.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/stat/display.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/stat/display.tt	(revision 2119)
@@ -0,0 +1,129 @@
+
+[% ostat = c.model('Accounts').db.get_object('stat', statname) %]
+<h3>[% ostat.get_attributes('description') || 'No description' | html %]</h3>
+<div >
+[% r = c.forward('/json/stat/index', [ statname ]) %]
+
+
+[% total = 0 %]
+[% FOREACH row = r %]
+    [% total = total + row.1 %]
+[% END %]
+
+[% FOREACH row = r %]
+[% IF loop.first %]
+<table class=datatable style="float:left; padding-top: 10px;">
+[% END %]
+
+<tr>
+<th>[% row.0 | html %]</th>
+<td>[% row.1 %]</td>
+<td>[% row.1 / total * 100 | format('%.2f') %] %</td>
+</tr>
+
+[% IF loop.last %]
+<tr>
+<th>Total:</th>
+<td>[% total %]</td>
+<td> 100 %</td>
+</tr>
+</table>
+[% END %]
+[% END %]
+
+<script>
+$(document).ready(function(){
+  // Our ajax data renderer which here retrieves a text file.
+  // it could contact any source and pull data, however.
+  // The options argument isn't used in this renderer.
+  
+    $.ajax({
+      // have to use synchronous here, else the function
+      // will return before the data is fetched
+      // async: false,
+      url: "[% c.uri_for('/json/stat', statname) %]",
+      dataType:"json",
+      success: function(data) {
+          var plot2 = $.jqplot('chartdpmt', 
+            [ data ],
+
+            {
+                title: '[% ostat.get_attributes('description') || 'No description' %]',
+                seriesDefaults: {
+                    shadow: false,
+                    renderer: jQuery.jqplot.PieRenderer,
+                    rendererOptions: { padding: 2, sliceMargin: 2, showDataLabels: true }
+                },
+            legend: {
+                renderer: $.jqplot.EnhancedLegendRenderer,
+                show:true,
+                location: 'e',
+            }
+          });
+        }
+    });
+
+    $.jqplot.config.enablePlugins = true;
+
+    $.ajax({
+      // have to use synchronous here, else the function
+      // will return before the data is fetched
+      // async: false,
+      url: "[% c.uri_for('/json/stat/history', statname) %]",
+      dataType:"json",
+      success: function(data) {
+          var plot2 = $.jqplot('charth', data.data, 
+            {
+                title: '[% ostat.get_attributes('description') || 'No description' %]',
+                axes: {
+                    xaxis: {
+                        renderer: $.jqplot.DateAxisRenderer, 
+                        rendererOptions:{
+                            tickRenderer:$.jqplot.CanvasAxisTickRenderer
+                        },
+                        tickOptions: {
+                            formatString:'%d/%m/%Y',
+                            fontSize:'10pt',
+                            fontFamily:'Tahoma',
+                            angle:-40
+                        },
+                        tickInterval:'1 months',
+                    },
+                    yaxis: {
+                        min: 0,
+                    },
+                },
+                seriesDefaults: {
+                    shadow: false,
+                    lineWidth: 2,
+                    rendererOptions: {
+                        padding: 1,
+                        sliceMargin: 2,
+                        showDataLabels: true,
+                        smooth: true
+                    },
+                    markerOptions:{style:'square', show: false, }
+                },
+                legend:{
+                    renderer: $.jqplot.EnhancedLegendRenderer,
+                    placement: 'outside',
+                    show: true
+                },
+                cursor:{
+                    zoom:true,
+                    tooltipLocation:'sw',
+                    looseZoom: true
+                },
+                series: data.series
+            }
+          );
+        } 
+    });
+});
+</script>
+
+<div id="chartdpmt" style="width:60%; height:500px; float:left"></div>
+</div>
+
+<div id="charth" style="width:80%; height:600px; clear: both"></div>
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/stat/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/stat/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/stat/index.tt	(revision 2119)
@@ -0,0 +1,11 @@
+[% FOREACH stat = c.model('Accounts').db.search_objects( 'stat', 'exported=true' ).sort %]
+    [% IF loop.first %]
+    <ul>
+    [% END %]
+    [% ostat = c.model('Accounts').db.get_object('stat', stat) %]
+    <li><a href="[% c.uri_for('/stat', stat) %]">[% ostat.get_attributes('description') || stat | html %]</a></li>
+    [% IF loop.last %]
+    </ul>
+    [% END %]
+[% END %]
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/accage/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/accage/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/accage/index.tt	(revision 2119)
@@ -0,0 +1,80 @@
+<form action="[% c.uri_for('/summary', 'accage' ) %]">
+<label for=sort>Trier par</label>
+<select name="sort">
+<option value=status
+    [% "selected" IF c.req.param('sort') == 'active' %]>[% l('Active/Non active') | html %]</option>
+<option value=department
+    [% "selected" IF c.req.param('sort') == 'department' %]>[% l('Department') | html %]</option>
+<option value=contratType
+    [% "selected" IF c.req.param('sort') == 'contratType' %]>[% l('Status') | html %]</option>
+</select>
+<br>
+<input name=inactive type=checkbox value=on [% 'checked' IF c.req.param('inactive') %]>
+<label for=inactive>[% l('Show inactive accounts') %]</label>
+<br>
+<input type=submit>
+</form>
+<script>
+$(document).ready(function(){
+    $.jqplot.config.enablePlugins = true;
+    $.ajax({
+      // have to use synchronous here, else the function
+      // will return before the data is fetched
+      //async: false,
+      url: "[% jsonurl %]",
+      dataType:"json",
+      success: function(data) {
+          var plot2 = $.jqplot('charth', data.data, 
+            {
+                title: 'Compte crÃ©Ã©s',
+                stackSeries: true,
+                axes: {
+                    xaxis: {
+                        renderer: $.jqplot.DateAxisRenderer, 
+                        rendererOptions:{
+                            tickRenderer:$.jqplot.CanvasAxisTickRenderer,
+                            stackedValue: true,
+                            tickInset: 0,
+                        },
+                        tickOptions: {
+                            formatString:'%Y',
+                            fontSize:'10pt',
+                            fontFamily:'Tahoma',
+                            angle:-40
+                        },
+                        tickInterval:'1 year',
+                    }
+                },
+                seriesDefaults: {
+                    renderer: $.jqplot.BarRenderer,
+                    lineWidth: 1,
+                    pointLabels: {show: true},
+                    rendererOptions: {
+                        padding: 1,
+                        sliceMargin: 1,
+                        showDataLabels: true,
+                        smooth: true,
+                        barPadding: 1,
+                    },
+                    //markerOptions:{style:'square'}
+                },
+                legend:{
+                    renderer: $.jqplot.EnhancedLegendRenderer,
+                    placement: 'outside',
+                    show: true
+                },
+                cursor:{
+                    zoom:true,
+                    tooltipLocation:'sw',
+                    looseZoom: true
+                },
+                series: data.series,
+            }
+          );
+        } 
+    });
+});
+</script>
+
+
+<div id="charth" style="width:80%; height:600px; clear: both"></div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/departments/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/departments/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/departments/index.tt	(revision 2119)
@@ -0,0 +1,81 @@
+<a name="top"></a>
+<h2>RÃ©partition par dÃ©partement</h2>
+
+[% dpmts = c.model('Accounts').db.search_objects('group', 'sutype=dpmt') %]
+
+<ul id="org-top" style="display: none">
+<li>Direction
+
+[% FOREACH dpmt = dpmts %]
+[% "<ul>" IF loop.first %]
+[% odpmt = c.model('Accounts').db.get_object('group', dpmt) %]
+<li>[% dpmt | html %]
+
+[% omanager = c.model('Accounts').db.get_object('user', odpmt.get_attributes('managedBy')) %]
+
+[% IF omanager %]
+<br>
+[% omanager %]
+[% END %]
+
+[% users = c.model('Accounts').db.fetchObjectInfo('user',
+   [ 'givenName', 'sn', 'managerContact', 'department', 'expire', 'description' ],
+   'active=1',
+   'department=' _ dpmt,
+) %]
+
+    [% FOREACH user = users.keys.sort %]
+    [% IF loop.first %]
+    <ul>
+    [% END %]
+    [% ouser = users.$user %]
+    <li>
+    [% IF ouser.department.0 != dpmt %]
+    <span style="font-style: italic;">
+    [% ELSE %]
+    <span>
+    [% END %]
+    [% ouser.givenName.0 _ ' ' _ ouser.sn.0 || ouser.description.0 || user | html %]
+    </span>
+    </li>
+    [% "</ul>" IF loop.last %]
+    [% END %] 
+
+[% "</ul>" IF loop.last %]
+[% END %]
+
+</li>
+</ul>
+
+<div id=chart></div>
+
+<style>
+
+div.orgChart {
+    overflow-x: scroll
+}
+
+div.orgChart .level1 {
+    font-size: 12px;
+}
+
+div.stack-container {
+    border: solid;
+}
+
+div.orgChart ul.stack > li {
+    margin-top: 1em;
+    font-size: 10px;
+    line-height: 1em;
+}
+</style>
+
+<script>
+$(function() {
+    $("#org-top").orgChart({
+        container: $("#chart"),
+        stack:     true,
+        depth:     2
+    });
+});
+</script>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/employment/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/employment/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/employment/index.tt	(revision 2119)
@@ -0,0 +1,94 @@
+
+<form action="[% c.uri_for() %]">
+<label for=key></label>[% l('Sort by') %]</label>
+<select name=key>
+<option value=department
+    [% "selected" IF c.req.param('key') == 'department' %]>[% l('Department') | html %]</option>
+<option value=contratType
+    [% "selected" IF c.req.param('key') == 'contratType' %]>[% l('Status') | html %]</option>
+<option value=allsite
+    [% "selected" IF c.req.param('key') == 'allsite' %]>[% l('Site') | html %]</option>
+</select><br>
+<label for=filter>[% l('Filter') %]</label>
+<input name=filter value=[% c.req.param('filter') | html %]>
+<br>
+<input name=past type=checkbox value=on id=past [% 'checked' IF c.req.param('past') %]>
+<label for=past>Afficher les dates passÃ©es</label>
+<br>
+<input name=stacked id=stacked type=checkbox value=on [% 'checked' IF c.req.param('stacked') %]>
+<label for=stacked>[% l('Stacked') %]</label>
+<input type=submit>
+</form>
+
+<div id="charth" style="width:80%; height:600px; clear: both"></div>
+<div id=info1b style="display: none; position: fixed; top: 0; right 100px;
+width: 300px; background-color: green;"></div>
+<script>
+$(document).ready(function(){
+    $.jqplot.config.enablePlugins = true;
+    $.ajax({
+      // have to use synchronous here, else the function
+      // will return before the data is fetched
+      //async: false,
+      url: "[% c.uri_for('/json/employment', {
+            key => c.req.param('key'), 
+            filter => c.req.param('filter')
+            past => c.req.param('past')
+        }) %]",
+      dataType:"json",
+      success: function(data) {
+          var plot2 = $.jqplot('charth', data.data, 
+            {
+                title: 'Evolution des status',
+                [% IF c.req.param('stacked') %]
+                stackSeries: true,
+                [% END %]
+                axes: {
+                    xaxis: {
+                        renderer: $.jqplot.DateAxisRenderer, 
+                        rendererOptions:{
+                            tickRenderer:$.jqplot.CanvasAxisTickRenderer,
+                        },
+                        tickOptions: {
+                            formatString:'%d/%m/%Y',
+                            fontSize:'10pt',
+                            fontFamily:'Tahoma',
+                            angle:-40
+                        },
+                        tickInterval:'6 months',
+                    },
+                    yaxis: {
+                        min: 0,
+                    },
+                },
+                seriesDefaults: {
+                    [% IF c.req.param('stacked') %]
+                    fill: true,
+                    [% END %]
+                    showMarker: false,
+                    shadow: false,
+                    lineWidth: 2,
+                    rendererOptions: {
+                        sliceMargin: 2,
+                        showDataLabels: true,
+                        smooth: true
+                    },
+                    //markerOptions:{style:'square'}
+                },
+                legend:{
+                    renderer: $.jqplot.EnhancedLegendRenderer,
+                    placement: 'outside',
+                    show: true
+                },
+                cursor:{
+                    zoom:true,
+                    tooltipLocation:'sw',
+                    looseZoom: true
+                },
+                series: data.series,
+            }
+          );
+        } 
+    });
+});
+</script>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/employmentbyperiod/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/employmentbyperiod/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/employmentbyperiod/index.tt	(revision 2119)
@@ -0,0 +1,165 @@
+
+<h3>Nombre de type statut par pÃ©riode</h3>
+
+<p>Nombre personne par statut (cumulÃ©) pendant la pÃ©riode indiquÃ©e:</p>
+
+<div>
+
+<form action="[% c.uri_for('/summary/employmentbyperiod') %]">
+DÃ©but: <input id=start name=start value="[% c.req.param('start') %]">
+Fin: <input id=end name=end value="[% c.req.param('end') %]">
+<br>
+<input name=atstart id=atstart value=yes 
+    type=checkbox [% "checked" IF c.req.param('atstart') %]>
+<label for=atstart>Uniquement les statuts commenÃ§ant pendant</label>
+<br>
+<input name=atend id=atend value=yes 
+    type=checkbox [% "checked" IF c.req.param('atend') %]>
+<label for=atend>Uniquement les statuts finissant pendant</label>
+<br>
+<input type=submit>
+</form>
+
+<script>
+$(function() {
+    $('#start').datepicker({
+        dateFormat: 'dd/mm/yy',
+    });
+    $('#end').datepicker({
+        dateFormat: 'dd/mm/yy',
+    });
+});
+</script>
+
+[% r = c.forward('/json/employmentbyperiod/index') %]
+
+
+[% total = 0 %]
+[% FOREACH row = r %]
+    [% total = total + row.1 %]
+[% END %]
+
+[% FOREACH row = r %]
+[% IF loop.first %]
+<table class=datatable style="float:left; padding-top: 10px;">
+[% END %]
+
+<tr>
+<th>[% row.0 | html %]</th>
+<td>[% row.1 %]</td>
+</tr>
+
+[% IF loop.last %]
+<tr>
+<th>Total:</th>
+<td>[% total %]</td>
+</tr>
+</table>
+[% END %]
+[% END %]
+
+<script>
+$(document).ready(function(){
+  // Our ajax data renderer which here retrieves a text file.
+  // it could contact any source and pull data, however.
+  // The options argument isn't used in this renderer.
+  
+    $.ajax({
+      // have to use synchronous here, else the function
+      // will return before the data is fetched
+      // async: false,
+      url: "[% c.uri_for('/json/employmentbyperiod', c.req.params ) %]",
+      dataType:"json",
+      success: function(data) {
+          var plot2 = $.jqplot('chartdpmt', 
+            [ data ],
+
+            {
+                title: '[% ostat.get_attributes('description') || 'No description' %]',
+                seriesDefaults: {
+                    shadow: false,
+                    renderer: jQuery.jqplot.BarRenderer,
+                    rendererOptions: {
+                        padding: 2,
+                        sliceMargin: 2,
+                        showDataLabels: true,
+                        varyBarColor: true
+                    },
+                },
+                axes:{
+                    xaxis:{
+                        tickOptions:{ 
+                            angle: -50
+                        },
+                        renderer: $.jqplot.CategoryAxisRenderer,
+                        tickRenderer: $.jqplot.CanvasAxisTickRenderer,
+                    }
+                },
+          });
+        }
+    });
+
+    $.jqplot.config.enablePlugins = true;
+
+    $.ajax({
+      // have to use synchronous here, else the function
+      // will return before the data is fetched
+      // async: false,
+      url: "[% c.uri_for('/json/stat/history', statname) %]",
+      dataType:"json",
+      success: function(data) {
+          var plot2 = $.jqplot('charth', data.data, 
+            {
+                title: '[% ostat.get_attributes('description') || 'No description' %]',
+                axes: {
+                    xaxis: {
+                        renderer: $.jqplot.DateAxisRenderer, 
+                        rendererOptions:{
+                            tickRenderer:$.jqplot.CanvasAxisTickRenderer
+                        },
+                        tickOptions: {
+                            formatString:'%d/%m/%Y',
+                            fontSize:'10pt',
+                            fontFamily:'Tahoma',
+                            angle:-40
+                        },
+                        tickInterval:'1 months',
+                    },
+                    yaxis: {
+                        min: 0,
+                    },
+                },
+                seriesDefaults: {
+                    shadow: false,
+                    lineWidth: 2,
+                    rendererOptions: {
+                        padding: 1,
+                        sliceMargin: 2,
+                        showDataLabels: true,
+                        smooth: true
+                    },
+                    //markerOptions:{style:'square'}
+                },
+                legend:{
+                    renderer: $.jqplot.EnhancedLegendRenderer,
+                    placement: 'outside',
+                    show: true
+                },
+                cursor:{
+                    zoom:true,
+                    tooltipLocation:'sw',
+                    looseZoom: true
+                },
+                series: data.series
+            }
+          );
+        } 
+    });
+});
+</script>
+
+<div id="chartdpmt" style="width:60%; height:500px; float:left"></div>
+</div>
+
+<div id="charth" style="width:80%; height:600px; clear: both"></div>
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/expire/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/expire/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/expire/index.tt	(revision 2119)
@@ -0,0 +1,88 @@
+
+<script>
+$(document).ready(function(){
+    $.jqplot.config.enablePlugins = true;
+    $.ajax({
+      // have to use synchronous here, else the function
+      // will return before the data is fetched
+      //async: false,
+      url: "[% c.uri_for('/json/expire') %]",
+      dataType:"json",
+      success: function(data) {
+          var plot2 = $.jqplot('charth', data.data, 
+            {
+                title: 'Expiration des comptes utilisateurs',
+                axes: {
+                    xaxis: {
+                        renderer: $.jqplot.DateAxisRenderer, 
+                        rendererOptions:{
+                            tickRenderer:$.jqplot.CanvasAxisTickRenderer,
+                            stackedValue: true,
+                        },
+                        tickOptions: {
+                            formatString:'%d/%m/%Y',
+                            fontSize:'10pt',
+                            fontFamily:'Tahoma',
+                            angle:-40
+                        },
+                        tickInterval:'6 months',
+                    },
+                    yaxis: {
+                        min: 0,
+                    },
+                },
+                seriesDefaults: {
+                    shadow: false,
+                    lineWidth: 2,
+                    rendererOptions: {
+                        padding: 1,
+                        sliceMargin: 2,
+                        showDataLabels: true,
+                        smooth: true
+                    },
+                    //markerOptions:{style:'square'}
+                },
+                legend:{
+                    placement: 'outside',
+                    show: true
+                },
+                cursor:{
+                    zoom:true,
+                    tooltipLocation:'sw',
+                    looseZoom: true
+                },
+                series: data.series,
+            }
+          );
+        } 
+    });
+});
+</script>
+
+<div id="charth" style="width:80%; height:600px; clear: both">
+</div>
+<h3>Expiration Ã  venir</h3>
+[% FOREACH ex = expirelist.keys.sort %]
+[% IF loop.first %]
+<table class=datalist>
+[% END %]
+<tr>
+<th>[% ex | html %]</th>
+<td>
+    [% FOREACH exuser = expirelist.$ex %]
+    [% IF loop.first %]
+    <ul>
+    [% END %]
+    <li>[% exuser.givenName.0 _ ' ' _ exuser.sn.0 || exuser.description.0 | html %] ([% exuser.contratType.0 || 'none' %])</li>
+    [% IF loop.last %]
+    </ul>
+    [% END %]
+    [% END %]
+</td>
+</tr>
+[% IF loop.last %]
+</table>
+[% END %]
+[% END %]
+</div>
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/offices/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/offices/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/offices/index.tt	(revision 2119)
@@ -0,0 +1,44 @@
+<h2>Annuaire</h2>
+
+<a name="top"></a>
+[% FOREACH site = office.keys.sort %]
+[% "<ul>" IF loop.first %]
+<li><a href="#[% site | html %]">[% sites.$site.description.0 || site | html %]</a></li>
+[% "</ul>" IF loop.last %]
+[% END %]
+[% FOREACH site = office.keys.sort %]
+[% s = office.$site.site %]
+
+<h3><a name="[% site | html %]">[% sites.$site.description.0 || site | html %]</a><a href="#top">â</a></h3>
+
+[% FOREACH off = office.$site.keys.sort %]
+    [% IF loop.first %]
+    <table border=1>
+    [% END %]
+<tr>
+<th>[% off %]</th>
+<td>
+
+[% FOREACH add = office.$site.$off  %]
+[% "<ul>" IF loop.first %]
+
+<li>
+[% "<b>" IF add.isMainAddress %]
+<a href="[% c.uri_for('/users', add.user, 'address', add.id) %]">
+[% add.displayName | html %]
+</a>
+[% "</b>" IF add.isMainAddress %]
+
+[% add.phone | html %]
+</li>
+
+[% "</ul>" IF loop.last %]
+[% END %]
+</td>
+</tr>
+    [% IF loop.last %]
+    </table>
+    [% END %]
+[% END %]
+
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/rh/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/rh/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/rh/index.tt	(revision 2119)
@@ -0,0 +1,201 @@
+
+<h3>Comptes actif par dÃ©partement</h3>
+<div >
+[% r = c.forward('/json/stat/dpmt') %]
+
+[% total = 0 %]
+[% FOREACH row = r %]
+    [% total = total + row.1 %]
+[% END %]
+
+[% FOREACH row = r %]
+[% IF loop.first %]
+<table class=datatable style="float:left; padding-top: 10px;">
+[% END %]
+
+<tr>
+<th>[% row.0 | html %]</th>
+<td>[% row.1 %]</td>
+<td>[% row.1 / total * 100 | format('%.2f') %] %</td>
+</tr>
+
+[% IF loop.last %]
+<tr>
+<th>Total:</th>
+<td>[% total %]</td>
+<td> 100 %</td>
+</tr>
+</table>
+[% END %]
+[% END %]
+
+<script>
+$(document).ready(function(){
+  // Our ajax data renderer which here retrieves a text file.
+  // it could contact any source and pull data, however.
+  // The options argument isn't used in this renderer.
+  
+    $.ajax({
+      // have to use synchronous here, else the function
+      // will return before the data is fetched
+      // async: false,
+      url: "[% c.uri_for('/json/stat/dpmt') %]",
+      dataType:"json",
+      success: function(data) {
+          var plot2 = $.jqplot('chartdpmt', 
+            [ data ],
+
+            {
+                    title: ' ',
+                seriesDefaults: {
+                shadow: false,
+                renderer: jQuery.jqplot.PieRenderer,
+                rendererOptions: { padding: 2, sliceMargin: 2, showDataLabels: true }
+            },
+            legend: { show:true, location: 'e' }
+
+                
+          });
+        }
+    });
+ 
+  // The url for our json data
+ 
+  // passing in the url string as the jqPlot data argument is a handy
+  // shortcut for our renderer.  You could also have used the
+  // "dataRendererOptions" option to pass in the url.
+});
+</script>
+
+<div id="chartdpmt" style="width:60%; height:500px; float:left"></div>
+</div>
+
+
+<h3 style="clear: both;">Comptes actifs par Statut</h3>
+
+[% r = c.forward('/json/stat/employment') %]
+
+[% total = 0 %]
+[% FOREACH row = r %]
+    [% total = total + row.1 %]
+[% END %]
+
+[% FOREACH row = r %]
+[% IF loop.first %]
+<table class=datatable style="float: left; padding-top: 10px;">
+[% END %]
+
+<tr>
+<th>[% row.0 | html %]</th>
+<td>[% row.1 %]</td>
+<td>[% row.1 / total * 100 | format('%.2f') %] %</td>
+</tr>
+
+[% IF loop.last %]
+<tr>
+<th>Total:</th>
+<td>[% total %]</td>
+<td> 100 %</td>
+</tr>
+</table>
+[% END %]
+[% END %]
+
+<script>
+$(document).ready(function(){
+  // Our ajax data renderer which here retrieves a text file.
+  // it could contact any source and pull data, however.
+  // The options argument isn't used in this renderer.
+  
+    $.ajax({
+      // have to use synchronous here, else the function
+      // will return before the data is fetched
+      url: "[% c.uri_for('/json/stat/employment') %]",
+      dataType:"json",
+      success: function(data) {
+        var plot2 = $.jqplot('chartemployment', 
+            [ data ],
+
+            {
+            title: ' ',
+            seriesDefaults: {
+            shadow: false,
+            renderer: jQuery.jqplot.PieRenderer,
+            rendererOptions: { padding: 2, sliceMargin: 2, showDataLabels: true }
+            },
+            legend: { show:true, location: 'e' }
+
+            });
+      }
+    });
+ 
+});
+</script>
+
+<div id="chartemployment" style="width:60%; height:500px; float: left;"></div>
+
+<h3 style="clear: both;">Comptes actifs par responsable</h3>
+
+[% r = c.forward('/json/stat/manager') %]
+
+[% total = 0 %]
+[% FOREACH row = r %]
+    [% total = total + row.1 %]
+[% END %]
+
+[% FOREACH row = r %]
+[% IF loop.first %]
+<table class=datatable style="float: left; padding-top: 10px;">
+[% END %]
+
+<tr>
+<th>
+[% ouser = c.model('Accounts').db.get_object('user', row.0) %]
+[% ouser || row.0 | html %]</th>
+<td>[% row.1 %]</td>
+<td>[% row.1 / total * 100 | format('%.2f') %] %</td>
+</tr>
+
+[% IF loop.last %]
+<tr>
+<th>Total:</th>
+<td>[% total %]</td>
+<td> 100 %</td>
+</tr>
+</table>
+[% END %]
+[% END %]
+
+<script>
+$(document).ready(function(){
+  // Our ajax data renderer which here retrieves a text file.
+  // it could contact any source and pull data, however.
+  // The options argument isn't used in this renderer.
+  
+    $.ajax({
+      // have to use synchronous here, else the function
+      // will return before the data is fetched
+      url: "[% c.uri_for('/json/stat/manager', { 'min' => 2 }) %]",
+      dataType:"json",
+      success: function(data) {
+        var plot2 = $.jqplot('chartmanager', 
+            [ data ],
+
+            {
+            title: ' ',
+            seriesDefaults: {
+            shadow: false,
+            renderer: jQuery.jqplot.PieRenderer,
+            rendererOptions: { padding: 2, sliceMargin: 2, showDataLabels: true }
+            },
+            legend: { show:true, location: 'e' }
+
+            });
+      }
+    });
+ 
+});
+</script>
+
+<div id="chartmanager" style="width:60%; height:500px; float:left;"></div>
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/tree/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/tree/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/summary/tree/index.tt	(revision 2119)
@@ -0,0 +1,155 @@
+
+<div id="d3viz" style="clear: both; border : solid;"></div>
+<script type="text/javascript">
+
+function loadgraph() {
+
+    var w = 1000,
+        h = 800;
+
+    $("#d3viz").empty();
+
+    var vis = d3.select("#d3viz").append("svg")
+        .attr("width", w)
+        .attr("height", h);
+
+    var force = self.force = d3.layout.force()
+        .distance(100)
+        .charge(-1000)
+        .size([w, h]);
+
+   //zoom
+    vis.append("rect")
+	    .attr("class", "overlay")
+	    .attr("width", w)
+	    .attr("height", h)
+	    .attr("fill", "white")
+	    .call(d3.behavior.zoom()
+	    .scaleExtent([0.3, 3])
+	    .on("zoom", function() {
+            vis.attr("transform", "translate(" + d3.event.translate +
+              ")scale(" + d3.event.scale + ")");
+            }));
+
+     vis = vis.append("g");
+
+    d3.json("[% c.uri_for('/json/tree', ci.id, { json => 1 }) %]", function(error, json) {
+
+        force
+            .nodes(json.nodes)
+            .links(json.links)
+            .start();
+
+     // build the arrow.
+     vis.append("svg:defs").selectAll("marker")
+         .data(["rel","sub"])
+       .enter().append("svg:marker")
+         .attr("stroke", arrowcolor() )
+         .attr("id", function(d) { return "end" + d ; })
+         .attr("viewBox", "-5 -13 10 10")
+         .attr("refX", 15)
+         .attr("refY", -1.5)
+         .attr("markerWidth", 16)
+         .attr("markerHeight", 16)
+         .attr("orient", "auto")
+       .append("svg:path")
+         .attr("d", "M-5,-7L5,-2L-5,3");
+
+    //build link + arrow 
+    var link = vis.selectAll(".link")
+        .data(json.links)
+        .enter().append("line")
+        .attr("stroke", function(d) {
+                return d.color ? d.color : 'black'
+        })
+	    .attr("stroke-dasharray", "0,2 1")
+        .attr("class", function(d) { return "link" + d.type; })
+        .attr("marker-end", function(d) { return "url(#end" + d.type + ")"; });
+
+    // build the tooltip.
+    var div = d3.select("#d3viz").append("div")
+            .attr("class", "tooltip")
+	    .style("opacity", 0);
+
+    //build node
+    var node = vis.selectAll(".node")
+        .data(json.nodes)
+        .enter().append("g")
+        .attr("class", "node")
+        .call(force.drag)
+        .on("contextmenu", function(d) {
+            div.transition()
+                .duration(700)
+                .style("opacity", .9);
+            div .html("<b>Name = </b>" + d.name)
+                .style("left", (d3.event.pageX) + "px")
+                .style("top", (d3.event.pageY - 28) + "px");
+	    d3.event.preventDefault();
+            })
+        .on("mouseout", function(d) {
+            div.transition()
+                .delay(1000)
+                .duration(300)
+                .style("opacity", 0);
+        });
+
+    node.append("image")
+        .attr("class", "circle")
+        .attr("xlink:href", function(d){ return d.img_href })
+        .attr("x", "-16px")
+        .attr("y", "-16px")
+        .attr("width", "32px")
+        .attr("height", "32px");
+
+    node.append("text")
+        .attr("class", "nodetext")
+        .attr("dx", 16)
+        .attr("dy", ".35em")
+        .text(function(d) { return d.label })
+        .on("click", openLink() );
+
+    force.on("tick", function() {
+      link.attr("x1", function(d) { return d.source.x; })
+          .attr("y1", function(d) { return d.source.y; })
+          .attr("x2", function(d) { return d.target.x; })
+          .attr("y2", function(d) { return d.target.y; });
+
+      node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
+    });
+    function openLink() {
+        return function(d) {
+            //window.open("[% c.uri_for('/ci/link/') %]" + d.label, "_self")
+        }
+    } 
+    //Arrow color
+    function arrowcolor() {
+        return function(d) {
+            var color = "black";
+	    if(d == "rel") {
+	         color = "red";
+            } else if(d == "sub") {
+                color = "green"; 
+            }
+	    return color;
+        }
+    }
+
+    //link color
+    function linkcolor() {
+        return function(d) {
+            var color = "black";
+	    if(d.type == "rel") {
+	         color = "red";
+            } else if(d.type == "sub") {
+                color = "green"; 
+            }
+	    return color;
+        }
+    }
+
+});
+
+}
+
+loadgraph();
+</script>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/tools/csv/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/tools/csv/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/tools/csv/index.tt	(revision 2119)
@@ -0,0 +1,35 @@
+[% l('This page allow you to create multiple object from information contains in
+a CSV file.
+
+The first line must contains the name of attributes, each following lines
+contains single value attribute for futur objects.
+
+For user, the object name can be omitted, object name will be built from
+firstname and lastname (sn and givenName).') | html_break %]
+
+<p>[% l('Example:') %]
+
+<pre>
+givenName,sn,company
+John,Smith,MyComp
+</pre>
+</p>
+
+<form action="[% c.uri_for('/tools', 'csv') %]" method="post"
+    enctype="multipart/form-data">
+
+<label for=otype>[% l('Type of object to create') %]</label>
+<select id=otype name=otype>
+[% FOREACH o = [ 'user', 'group' ] %]
+[% IF c.model('Accounts').db.check_acl(o, '@CREATE', 'w') %]
+<option value="[% o %]">[% c.model('Accounts').obj_to_label(o) |html %]
+    </option>
+[% END %]
+[% END %]
+</select>
+<br>
+<label for=file>[% l('Select a CSV file') | html %]</label>
+<input type="file" name="csv" id="csvAccount">
+<br>
+<input type="submit">
+</form>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/tools/csv/parseCSV.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/tools/csv/parseCSV.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/tools/csv/parseCSV.tt	(revision 2119)
@@ -0,0 +1,46 @@
+[% IF failure %]
+
+<p class=error>[% l('One or multiples error occurs during objects creation, please check
+input data:') | html %]</p>
+
+[% FOREACH item = failures %]
+[% IF loop.first %]
+<li>
+[% END %]
+Line [% item.line %]: [% item.error | html %]<br>
+[% IF loop.last %]
+</li>
+[% END %]
+[% END %]
+
+[% ELSE # IF failure%]
+
+<p>[% l('The follwing objects has been created:') %]</p>
+
+[% FOREACH item = results %]
+[% IF loop.first %]
+<table border=1>
+<tr>
+<th>[% l('Object Name') %]</th>
+[% FOREACH attr = attrs %]
+<th>[% attr | html %]</th>
+[% END %]
+</tr>
+[% END %]
+<tr>
+[% obj = c.model('Accounts').db.get_object(otype, item.objectName) %]
+<td>
+<a href="[% c.model('Accounts').obj_to_uri(otype, obj.id) %]">[% obj.id %]</a>
+</td>
+[% FOREACH attr = attrs %]
+<th>[% obj.get_attributes(attr) %]</th>
+[% END %]
+</tr>
+
+[% IF loop.last %]
+</table>
+[% END %]
+
+[% END %]
+
+[% END # IF failure %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/tools/loadnew/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/tools/loadnew/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/tools/loadnew/index.tt	(revision 2119)
@@ -0,0 +1,32 @@
+<form action="[% c.uri_for('/tools', 'loadnew') %]" method="post"
+    enctype="multipart/form-data">
+
+<label for=otype>[% l('Type of object to create:') %]</label>
+<br>
+<select id=otype name=otype>
+[% FOREACH o = c.model('Accounts').db.list_supported_objects.sort %]
+[% label = c.model('Accounts').obj_to_label(o) %]
+[% NEXT IF NOT label %]
+[% IF c.model('Accounts').db.check_acl(o, '@CREATE', 'w') %]
+<option value="[% o %]">[% label |html %]
+    </option>
+[% END %]
+[% END %]
+</select>
+<br>
+<label for=name>[% l('Object name (optional):') %]</label>
+<br>
+<input type="text" name="name" id="name">
+<br>
+<fieldset>
+    <legend>[% l('Object data:') %]</legend>
+<label for=file>[% l('You can load a existing file...') | html %]</label>
+<br>
+<input type="file" name="input" id="inputAccount">
+<br>
+<label for=content>[% l('or paste the content here:') %]</label>
+<br>
+<textarea id=content name=content rows="10" cols="80"></textarea>
+</fieldset>
+<input type="submit">
+</form>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/tools/loadpasswd/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/tools/loadpasswd/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/tools/loadpasswd/index.tt	(revision 2119)
@@ -0,0 +1,44 @@
+<form action="[% c.uri_for('/tools', 'loadpasswd') %]" method="post"
+    enctype="multipart/form-data">
+
+<p>[% l('The file format must similar to UNIX standard password, only the two
+first column are used.') %]</p>
+
+<pre>
+username1:password1:....
+username2:password2:....
+</pre>
+
+<p>[% l('When password are encrypted they are injected only in
+the main database. This allow users to connect to this application then changing
+their password. (only crypt MD5 is supported' ) %]</p>
+<label for=crypt>[% l('Encrypted Password') | html %]</label>
+<input type=checkbox name=crypt>
+<br>
+<label for=crypt>[% l('Dont check for weak passord') | html %]</label>
+<input type=checkbox name=force>
+<br>
+<label for=file>[% l('You can load a existing file...') | html %]</label>
+<br>
+<input type="file" name="input" id="inputAccount">
+<br>
+<input type="submit">
+</form>
+
+[% FOREACH res = Pass %]
+[% IF loop.first %]
+<p>[% l('Results:') %]</p>
+<table border=1>
+<tr>
+<td>[% l('Username') | html %]</td>
+<td>[% l('Results') | html %]</td>
+</tr>
+[% END %]
+<tr>
+<td>[% res.u | html %]</td>
+<td>[% res.r | html %]</td>
+</tr>
+[% IF loop.last %]
+</table>
+[% END %]
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/users/Display.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/users/Display.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/users/Display.tt	(revision 2119)
@@ -0,0 +1,13 @@
+<!-- $Id$ -->
+[% IF NOT user %]
+No user [% username | html %] found.
+[% ELSE %]
+    
+[% INCLUDE 'users/object_header.tt' %]
+
+<div id="objectform" class="objectform">
+[% INCLUDE 'includes/form.tt'
+    form = form
+    form_uri = c.uri_for('/' _ c.req.path) %]
+</div>
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/users/address.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/users/address.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/users/address.tt	(revision 2119)
@@ -0,0 +1,89 @@
+<!-- $Id$ -->
+[% IF NOT user %]
+No user [% username | html %] found.
+[% ELSE %]
+    
+[% INCLUDE 'users/object_header.tt' %]
+
+<div id="objectform" class="objectform">
+<h2>Adresses postales:</h2>
+<table border="0" id="user_address_list">
+    [% FOREACH idaddress = user.get_c_field('otheraddress') %]
+    [% laddress = c.model('Accounts').db.get_object('address', idaddress) %]
+    <tr>
+        <td class=user_address_cel>
+
+            [% loop.count %]:
+            [% IF address.id != idaddress %]
+            <a href="[% c.uri_for(username, 'address', idaddress) %]">
+            [% END %]
+
+            <span [% 'class="unexported"' IF laddress.get_c_field('unexported') %]>
+            [% "<b>" IF laddress.get_c_field('isMainAddress') %]
+            [% laddress.get_c_field('l') || 'N/A' | html %]
+            [% IF laddress.get_c_field('physicalDeliveryOfficeName') %]
+            - [% laddress.get_c_field('physicalDeliveryOfficeName') | html %]
+            [% END %]
+            </span>
+
+            [% IF address.id != idaddress %]
+            [% "</b>" IF laddress.get_c_field('isMainAddress') %]
+            </a>
+            [% END %]
+        </td>
+        <td align="center">
+
+        [% IF laddress.base.check_acl(laddress, '@DELETE', 'w') %]
+        <form action="[% c.uri_for(username, 'address') %]" method="POST"
+            style="display:inline">
+        <input type="hidden" name="del_addr" value="[% laddress.id | html %]">
+        <input type="image" value="Effacer" title="Supprimer" alt="effacer"
+            src="[% c.uri_for('/static', 'images', 'edit-delete.png') %]"
+            class="attrbutton"
+        >
+        </form>
+        </td>
+        [%- END -%]
+
+        [%- IF laddress.base.check_acl(laddress, 'isMainAddress', 'w') -%]
+        [% IF NOT laddress.get_c_field('isMainAddress') %]
+        <td>
+        <form action="[% c.uri_for('/users', username, 'address') %]" method="POST"
+            style="display:inline;">
+        <input type="hidden" name="main" value="[% laddress.id | html %]">
+        <input type="image" value="" title="Passer en principale" alt="effacer"
+            src="[% c.uri_for('/static', 'images', 'emblem-favorite.png') %]"
+            class="attrbutton"
+            style="display:inline;"
+        >
+        </form>
+        </td>
+        [% END %]
+        [% END %]
+
+    </tr>
+    [% END %]
+    [% IF NOT c.req.param('add_addr') %]
+    [% IF c.model('Accounts').db.check_acl(user, 'l', 'w') &&
+          c.model('Accounts').db.check_acl('address', '@CREATE', 'w') %]
+    <tr><td colspan=3 align="center">
+    <form action="[% c.uri_for('/users', username, 'address') %]" method="POST">
+    <input type="hidden" name="add_addr" value="1">
+    <input type="submit" value="Ajouter une addresse">
+    </form>
+    </td></tr>
+    [% END %]
+    [% END %]
+    </table>
+
+
+[% IF address %]
+
+<form action="[% c.uri_for('/users', username, 'address', address.id) %]" method="POST">
+[% INCLUDE 'users/address_form.tt' %]
+</form>
+
+[% END %][% # IF address %]
+
+</div>
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/users/address_form.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/users/address_form.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/users/address_form.tt	(revision 2119)
@@ -0,0 +1,87 @@
+<!-- $Id$ -->
+<table border="1">
+
+<tr><th colspan=2>
+[% IF  address.get_c_field('unexported') %]
+<img src="[% c.uri_for('/static', 'images', 'mail-mark-junk.png') %]">
+[% ELSE %]
+<img src="[% c.uri_for('/static', 'images', 'mail-mark-unread.png') %]">
+[% END %]
+
+[% IF  address.get_c_field('isMainAddress') %]
+
+        <img src="[% c.uri_for('/static', 'images', 'gtk-home.png') %]"
+            title="Adresse principale" alt="Adresse principale"
+        >
+[% END %]
+[% address.get_c_field('l') | html %]
+[% IF address.get_c_field('physicalDeliveryOfficeName') %]
+- [% address.get_c_field('physicalDeliveryOfficeName') | html %]
+[% END %]
+</th></tr>
+
+[% FOREACH attrs IN [ 'site', 'description', 'isMainAddress',
+    'contactOnly', 'expire', 'physicalDeliveryOfficeName', 'telephoneNumber' ] %]
+<tr>
+    <th>
+        [% form.attr_label(attrs) %]
+
+        [% IF form.attr_hint(attrs) %]
+        <span
+            title="[% form.attr_hint(attrs) | html %]"
+            class="hint">?</span>
+        [% END %]
+
+    </th>
+    <td>[% form.attr_field(attrs) %]</td>
+</tr>
+[% END %]
+
+[% IF NOT c.req.param('add_addr') %]
+<tr>
+    <th>
+        [% form.attr_label('unexported') %]
+
+        [% IF form.attr_hint('unexported') %]
+        <span
+            title="[% form.attr_hint('unexported') | html %]"
+            class="hint">?</span>
+        [% END %]
+    </th>
+    <td>[% form.attr_field('unexported', 'checkbox') %]</td>
+</tr>
+[% ELSE %]
+<input type="hidden" value="1" name="unexported">
+[% END %]
+
+[% IF address.get_c_field('site') %]
+[% fieldtype = 'label' %]
+[% END %]
+<tr><td colspan=2>
+    Les champs suivants ne sont Ã  remplir que si le site n'est pas prÃ©cisÃ©
+</td></tr>
+
+[% FOREACH attrs IN [ 'streetAddress', 'postOfficeBox', 'postalCode', 'l', 'st',
+    'facsimileTelephoneNumber', 'co' ] %]
+<tr>
+    <th>
+        [% form.attr_label(attrs) %]
+        [% IF form.attr_hint(attrs) %]
+        <span
+            title="[% form.attr_hint(attrs) | html %]"
+            class="hint">?</span>
+        [% END %]
+    </th>
+    <td>[% form.attr_field(attrs, fieldtype) %]</td>
+</tr>
+[% END %]
+
+[% IF form.write_attributes %]
+<tr>
+    <th></th>
+    <td>
+    [% form.submit %]
+    </td>
+</tr>
+[% END %]
+</table>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/users/employment.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/users/employment.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/users/employment.tt	(revision 2119)
@@ -0,0 +1,155 @@
+[% INCLUDE 'users/object_header.tt' %]
+
+<div id="objectform" class="objectform">
+
+<div id=timeline style="width: 80%"></div>
+
+<script>
+$(function() {
+    $.ajax({
+        url: "[% c.uri_for('/json/timeline', username) %]",
+        dataType: "json",
+        success: function(data) {
+            var t2 = Timeframe("#timeline")
+                .start(data.start)
+                .end(data.end)
+                .majorTicks(5, "years")
+                .minorTicks(1, "year");
+            t2.addCategory(data.employments)
+            t2.draw();
+        }
+    });
+       
+});
+</script>
+
+<h3>
+[% IF employment %]
+Statut du [% employment.get_attributes('firstday') | html %]
+[% IF employment.get_attributes('lastday') %][% l('to') %] [% employment.get_attributes('lastday') | html %][% END %]
+[% ELSE %]
+[% l('New employment') | html %]
+[% END %]
+</h3>
+
+[% BLOCK formattr %]
+<tr>
+    <th>
+        [% thisform.attr_label(attr) |html %]
+        [% IF thisform.attr_hint(attr) %]
+        <span
+            title="[% thisform.attr_hint(attr) | html %]"
+            class="hint">?</span>
+        [% END %]
+    </th>
+    <td>[% thisform.attr_field(attr) %]</td>
+</tr>
+[% END %]
+
+<div style="display: inline-block;">
+    <form action="[% c.uri_for('/users', username, 'employment', employment.id || '@create') %]" method="POST">
+    <table border=1>
+    [% FOREACH attr =  form.attributes %]
+        [% INCLUDE 'formattr' thisform = form attr = attr %] 
+    [% END %]
+    [% FOREACH attr =  forme.attributes %]
+        [% INCLUDE 'formattr' thisform = forme attr = attr %] 
+    [% END %]
+    [% IF form.write_attributes OR  forme.write_attributes %]
+        <tr><td colspan=2>[% forme.submit(1) %][% form.submit(1) %]
+        <input type=submit
+            value="[% IF employment %]Enregistrer[% ELSE %]Ajouter...[%END %]">
+        </td></tr>
+    [% END %]
+    </table>
+    </form>
+</div>
+
+
+<div id=listemp style="display: inline-block; border: outset; padding-left: 20px; padding-right: 20px;">
+[% IF c.stash.user.listEmployment %]
+    <h3>Listes de status:</h3>
+
+    [% IF employment %]
+    <a href="[% c.uri_for('/users', username, 'employment', '@new') %]">Ajouter...</a>,
+    [% END %]
+
+    [% FOREACH e = user.listEmployment %]
+        [% IF loop.first %]
+        <a href="[% c.uri_for('/users', username, 'employment', '@duplicate') %]">Renouveler...</a>
+        <ul>
+        [% END %]
+
+        [% co = user.base.get_object('employment', e) %]
+        <li[% IF co.id == employment.id %] class=active[% END %]>
+        [% IF NOT co.get_attributes('state') %]*[% END %]
+
+        <a href="[% c.uri_for('/users', user.id, 'employment', co.id) %]">
+        [% co.get_attributes('contratType') %]
+
+        [% IF co.get_attributes('lastday') %]
+        [% l('From') %] [% co.get_attributes('firstday') %]
+        [% l('to') %] [% co.get_attributes('lastday') %]
+        [% ELSE %]
+        [% l('Since') %] [% co.get_attributes('firstday') %]
+        [% END %]
+        </a>
+
+        [% IF co.get_attributes('state') > 0 %]
+        <form method=POST action="[% c.uri_for('/users', username, 'employment') %]"
+            onSubmit="return confirm('DÃ©truire le statut commenÃ§ant [% co.get_attributes('firstday') %] ?') ? true : false">
+        <input type=hidden name=delemployment value="[% co.id %]">
+        <input type=submit value="Supprimer">
+        </form>
+        [% END %]
+
+        </li>
+
+        [% IF loop.last %]
+        </ul>
+        [% END %]
+    [% END %]
+[% END %]
+
+    <h3>[% l('Current status') %]:</h3>
+
+    <table>
+    [% FOREACH attribute = [ 'company', 'department', 'contratType', 'manager', ] %]
+    [% formrh = c.model('AttrForms', 'user', user ) %]
+    <tr>
+        <th>
+            [% formrh.attr_label(attribute) |html %]
+            [% IF formrh.attr_hint(attribute) %]
+            <span
+                title="[% formrh.attr_hint(attribute) | html %]"
+                class="hint">?</span>
+            [% END %]
+        </th>
+        <td>[% user.get_attributes(attribute) | html  %]</td>
+    </tr>
+    [% END %]
+
+    <tr><th>[% l('Created') %]</th>
+        <td>[% user.get_attributes('create') | html  %]</td></tr>
+    <tr><th>[% l('Arrival') %]</th>
+        <td>[% user.get_attributes('startEmployment') | html  %]</td></tr>
+    <tr><th>[% l('Leave') %]</th>
+        <td>[% user.get_attributes('endEmployment') | html  %]</td></tr>
+    <tr>
+        <th>
+            [% formrh.attr_label('expire') |html %]
+            [% IF formrh.attr_hint('expire') %]
+            <span
+                title="[% formrh.attr_hint('expire') | html %]"
+                class="hint">?</span>
+            [% END %]
+        </th>
+        <td>[% user.get_attributes('expire') | html  %]</td>
+    </tr>
+    <tr><th>[% l('Duration') %]</th>
+        <td>[% user.get_attributes('employmentLengthText') | html  %]</td></tr>
+    </table>
+
+</div>
+
+</div><!-- objectform -->
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/users/groups.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/users/groups.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/users/groups.tt	(revision 2119)
@@ -0,0 +1,72 @@
+<!-- $Id$ -->
+[% IF NOT user %]
+No user [% username | html %] found.
+[% ELSE %]
+    
+[% INCLUDE 'users/object_header.tt' %]
+
+
+<div id="objectform" class="objectform">
+<h2>Appartenance aux groupes:</h2>
+[% modallow = c.model('Accounts').db.check_acl(user, 'memberOf', 'w') %]
+
+<table border="1">
+<tr>
+<th>Membre de:</th>
+<th>Autres groupes</th>
+</tr>
+<tr>
+<td valign="TOP">
+[% IF c.model('Accounts').db.check_acl(user, 'memberOf', 'r') %]
+[% FOREACH g = user.get_c_field('memberOf').sort %]
+[% IF modallow %]
+[% IF user.get_attributes('department') == g OR 
+      user.get_attributes('contratType') == g %]
+<img src="[% c.uri_for('/static', 'images', 'changes-prevent.png') %]"
+    width="24" height="24" title="Ce groupe est gÃ©rÃ© via un autre attribut">
+[% ELSE %]
+<form action="[% c.uri_for(username, subform) %]" method="POST" style="display: inline">
+<input type="hidden" name="delgroup" value="[% g | html %]">
+<input type="image" src="[% c.uri_for('/static', 'images', 'group_del.png') %]" width="24" height="24">
+</form>
+[% END %]
+[% END %]
+<a href="[% c.uri_for('/groups', g) %]">[% g | html %]</a><br>
+[% IF modallow %]
+[% END %]
+[% END %]
+[% ELSE %]
+[% perm_denied %]
+[% END %]
+</td>
+<td valign="TOP">
+[% IF c.model('Accounts').db.check_acl(user, 'memberOf', 'r') %]
+<form action="#">
+filter:<br>
+<input type="text" name="addgroup" id="gfilter" value="[% c.req.param('gfilter') | html %]">
+<input type=submit>
+<script>
+$(function() {
+    $('#gfilter').autocomplete({
+        delay: 200,
+        minLength: 0,
+        source: '[% c.uri_for('/json/refval', 'user', 'memberOf') %]',
+    })
+    .autocomplete( "instance" )._renderItem = function( ul, item ) {
+        return $( "<li>" )
+            .append( "<a>" + item.label + " (" + item.value +  ")</a>" )
+            .appendTo( ul );
+    };
+    $( "#gfilter" ).focus(function() { $( "#gfilter" ).autocomplete("search", "" ); });
+    $( "#gfilter" ).click(function() { $( "#gfilter" ).autocomplete("search", "" ); });
+});
+</script>
+</form>
+<br>
+[% END %]
+</td>
+</tr>
+</table>
+
+</div>
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/users/index.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/users/index.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/users/index.tt	(revision 2119)
@@ -0,0 +1,10 @@
+<!-- $Id$ -->
+<div id="filter">
+[% INCLUDE 'includes/filter.tt'
+   formuri = c.uri_for()
+%]
+</div>
+
+<div id="list_objects">
+[% INCLUDE 'includes/list_objects.tt' %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/users/mail.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/users/mail.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/users/mail.tt	(revision 2119)
@@ -0,0 +1,145 @@
+<!-- $Id$ -->
+[% IF NOT user %]
+No user [% username | html %] found.
+[% ELSE %]
+
+[% INCLUDE 'users/object_header.tt' %]
+
+<div id="objectform" class="objectform">
+
+<h2>E-mails:</h2>
+
+<form action="[% c.uri_for(user.id, 'mail') %]" method="POST">
+<input type=hidden name="usermail" value=1>
+<table border=1>
+[% IF c.model('Accounts').db.check_acl(user, 'mail', 'r') %]
+<tr><th>EMail</th>
+<td>
+[% IF c.model('Accounts').db.check_acl(user, 'mail', 'w') %]
+[% showsubmit = 1 %]
+<input type="text" name="mail" value="[% user.get_c_field('mail') | html %]">
+[% ELSE %]
+[% user.get_c_field('mail') | html %]
+[% END %]
+</td>
+</tr>
+[% END %]
+
+[% IF c.model('Accounts').db.check_acl(user, 'otherEmail', 'r') %]
+<tr><th>EMail extÃ©rieur</th>
+<td>
+[% IF c.model('Accounts').db.check_acl(user, 'otherEmail', 'w') %]
+[% showsubmit = 1 %]
+<input type="text" name="otherEmail" value="[% user.get_c_field('otherEmail') | html %]">
+[% ELSE %]
+[% user.get_c_field('otherEmail') | html %]
+[% END %]
+</td>
+</tr>
+[% END %]
+
+[% IF c.model('Accounts').db.check_acl(user, 'aliases', 'r') %]
+<tr>
+<th>Aliases</th>
+<td>
+Existants (activÃ©/dÃ©sactivÃ©):<br>
+[% FOREACH alias = user.get_c_field('aliases').sort %]
+[% IF c.model('Accounts').db.check_acl(user, 'aliases', 'w') %]
+[% showsubmit = 1 %]
+[% oalias = c.model('Accounts').db.get_object('aliases', alias) %]
+<input type="text" name="aliases" value="[% alias | html %]" id="[% 'aliases' _ loop.count %]">
+<span id="[% 'aliases_span' _ loop.count %]"></span>
+
+<script>
+$( function() {
+    $( "#aliases[% loop.count %]" ).on('input', function() {
+        $.ajax({
+            method: "POST",
+            url: '[% c.uri_for('/ajax', 'objexist', 'aliases') %]',
+            data: { val: $(this).val(), attr: 'forward' },
+        }). done(function( msg ) {
+            $( "#aliases_span[% loop.count %]" ).html (msg);
+        });
+    });
+});
+</script>
+[% ELSE %]
+[% alias | html %]
+[% END %]
+
+<input type=checkbox name="expaliases" value="[% alias %]"
+[% 'checked' IF oalias.get_attributes('exported') %]>
+<br>
+
+[% END %]
+[% IF c.model('Accounts').db.check_acl(user, 'aliases', 'w') %]
+[% showsubmit = 1 %]
+Ajouter un alias:<br>
+<input type="text" name="aliases" value="" id="aliases0">
+<span id="[% 'aliases_span' _ 0 %]"></span>
+
+<script>
+$( function() {
+    $( "#aliases0" ).on('input', function() {
+        $.ajax({
+            method: "POST",
+            url: '[% c.uri_for('/ajax', 'objexist', 'aliases') %]',
+            data: { val: $(this).val(), attr: 'forward' },
+        }). done(function( msg ) {
+            $( "#aliases_span0" ).html (msg);
+        });
+    });
+});
+</script>
+
+[% END %]
+</td>
+</tr>
+[% END %]
+
+
+[% IF c.model('Accounts').db.check_acl(user, 'forward', 'r') %]
+<tr>
+<th>Renvois</th>
+
+<td>
+[% oalias = c.model('Accounts').db.get_object('aliases', user.id) %]
+[% IF c.model('Accounts').db.check_acl(user, 'forward', 'w') %]
+[% showsubmit = 1 %]
+<input type="text" name="forward" value="[% user.get_c_field('forward').join(',') | html %]">
+[% IF oalias %]
+<input type=checkbox name="expaliases" value="[% user.id %]"
+[% 'checked' IF oalias.get_attributes('exported')  %]>
+[% END %]
+[% ELSE %]
+[% user.get_c_field('forward').join(',') | html %]
+[% END %]
+</td>
+</tr>
+[% END %]
+
+[% IF c.model('Accounts').db.check_acl(user, 'revaliases', 'r') %]
+<tr>
+<th>RevAliases</th>
+<td>
+[% IF c.model('Accounts').db.check_acl(user, 'revaliases', 'w') %]
+[% showsubmit = 1 %]
+<input type="text" name="revaliases" value="[% user.get_c_field('revaliases') | html %]">
+[% ELSE %]
+[% user.get_c_field('revaliases') | html %]
+[% END %]
+</td>
+</tr>
+[% END %]
+
+[% IF showsubmit %]
+<tr><td colspan=2>
+<input type="submit">
+</td>
+</tr>
+[% END %]
+</table>
+</form>
+
+</div>
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/users/my.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/users/my.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/users/my.tt	(revision 2119)
@@ -0,0 +1,77 @@
+<!-- $Id$ -->
+[% IF NOT user %]
+No user [% username | html %] found.
+[% ELSE %]
+
+[% INCLUDE 'users/object_header.tt' %]
+
+<div id="objectform" class="objectform">
+[% db = c.model('Accounts').db %]
+
+[% FOREACH h = db.search_objects('group', 'managedBy=' _ username) %]
+[% IF loop.first %]
+<p>Mes groupes</p>
+<table border=1>
+<tr>
+<th>Groupe:</th>
+<th>Desc.</th>
+</tr>
+[% END %]
+[% ogroup = db.get_object('group', h) %]
+<tr><td>
+<a href="[% c.uri_for('/groups', h) %]">[% h | html %]</a>
+</td>
+<td>
+[% ogroup.get_attributes('description') | truncate(30) | html %]
+</td>
+</tr>
+[% IF loop.last %]
+</table>
+[% END %]
+[% END %]
+
+[% FOREACH h = db.search_objects('user', 'manager=' _ username) %]
+[% IF loop.first %]
+<p>Personnels gÃ©rÃ©s</p>
+<table border=1>
+<tr>
+<th>Nom:</th>
+</tr>
+[% END %]
+[% ouser = db.get_object('user', h) %]
+<tr><td>
+<a href="[% c.uri_for('/users', h) %]">[% ouser.get_attributes('displayName') | html %]</a>
+[% INCLUDE user_flag ouser = ouser %]
+</td>
+</tr>
+[% IF loop.last %]
+</table>
+[% END %]
+[% END %]
+
+[% FOREACH h = db.search_objects('nethost', 'owner=' _ username) %]
+[% IF loop.first %]
+<p>Mes ordinateurs</p>
+<table border=1>
+<tr>
+<th>Nom:</th>
+<th>Description:</th>
+</tr>
+[% END %]
+[% ohost = db.get_object('nethost', h) %]
+<tr>
+<td>
+<a href="[% c.uri_for('/nethosts', h) %]">[% h | html %]</a>
+</td>
+<td>
+[% ohost.get_attributes('description') | html %]
+</td>
+</tr>
+[% IF loop.last %]
+</table>
+[% END %]
+[% END %]
+
+</div>
+
+[% END %]
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/users/object_header.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/users/object_header.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/users/object_header.tt	(revision 2119)
@@ -0,0 +1,54 @@
+<!-- $Id$ -->
+
+[% INCLUDE 'includes/object_header.tt'
+    headerobj = user
+    %]
+
+<div id="oaction">
+[% IF c.user.username != username %]
+[% IF c.model('Accounts').db.check_acl(user, 'exported', 'w') %]
+    [% IF NOT user.get_c_field('exported') %]
+    <form method="POST" action="[% c.uri_for('/users', username, subform) %]"
+          onSubmit="return confirm('Activer l\'utilisateur [% username %] ?') ? true : false">
+    <input type="image" name="make_active" value="Activer" title="Activer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-enable.png') %]">
+    <input type="hidden" value="activer" name="make_active">
+    </form>
+    [% ELSE %]
+    <form method="POST" action="[% c.uri_for('/users', username, subform) %]"
+          onSubmit="return confirm('DÃ©sactiver l\'utilisateur [% username %] ?') ? true : false">
+    <input type="image" name="make_inactive" value="DÃ©sactiver" title="DÃ©sactiver"
+    alt="dÃ©sactiver" src="[% c.uri_for('/static', 'images', 'edit-disable.png') %]">
+    <input type="hidden" value="dÃ©sactiver" name="make_inactive">
+    </form>
+    [% END %]
+[% END %]
+[% IF c.model('Accounts').db.check_acl(user, 'DELETE', 'w') %]
+    <form method="POST" action="[% c.uri_for('/users', username, subform) %]"
+          onSubmit="return confirm('DÃ©truire l\'utilisateur [% username %] ?') ? true : false">
+    <input type="image" name="delete" value="Supprimer" title="Supprimer"
+    alt="effacer" src="[% c.uri_for('/static', 'images', 'edit-delete.png') %]">
+    <input type="hidden" value="DETRUIRE" name="delete">
+    </form>
+[% END %]
+[% END %]
+</div>
+
+<div id="omenu">
+        <a href="[% c.uri_for('/users', username) %]"><span [% 'id="oactive"' IF subform == '' || subform == 'employment' %]>Statut RH</span></a>
+        <a href="[% c.uri_for('/users',username, 'sys') %]"><span [% 'id="oactive"' IF subform == 'sys' %]>SystÃšme</span></a>
+        <a href="[% c.uri_for('/users', username, 'groups') %]"><span [% 'id="oactive"' IF subform == 'groups' %]>Groupes</span></a>
+[% IF c.config.features.user_addresses %]
+        <a href="[% c.uri_for('/users', username, 'address') %]"><span [% 'id="oactive"' IF subform == 'address' %]>Adresses</span></a>
+[% END %]
+[% IF c.config.features.user_email %]
+        <a href="[% c.uri_for('/users', username, 'mail') %]"><span [% 'id="oactive"' IF subform == 'mail' %]>eMail</span></a>
+[% END %]
+[% IF c.config.features.user_my %]
+        <a href="[% c.uri_for('/users', username, 'my') %]"><span [% 'id="oactive"' IF subform == 'my' %]>My</span></a>
+[% END %]
+[% IF c.model('Accounts').db.check_acl(user, 'userPasswd', 'w') %]
+        <a href="[% c.uri_for('/users', username, 'passwd') %]"><span [% 'id="oactive"' IF subform == 'passwd' %]>Mot de passe</span></a>
+[% END %]
+</div>
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/html/users/passwd.tt
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/html/users/passwd.tt	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/html/users/passwd.tt	(revision 2119)
@@ -0,0 +1,92 @@
+<!-- $Id$ -->
+[% INCLUDE 'users/object_header.tt' %]
+
+<script type="text/javascript">
+function check_passwd(fullcheck) {
+    if ($("#passwd").val()) {
+        if ($("#passwd").val() == $("#cpasswd").val() || fullcheck) {
+            $.ajax({
+                url: '[% c.uri_for('/ajax', 'cracklib', username) %]',
+                data: { passwd: $("#passwd").val(), cpasswd: $("#cpasswd").val() },
+            }).done(function(msg) {
+                $('#perror').html(msg);
+            });
+        } else {
+            $('#perror').html('[% l('Password are different') %]');
+        }
+    } else {
+        $('#perror').html('[% l('Please enter password') %]');
+    }
+}
+
+$(function () {
+
+$("#passwd").on( 'keyup', function() { check_passwd(1) });
+$("#cpasswd").on('keyup', function() { check_passwd(0) });
+
+});
+</script>
+
+<div id="objectform" class="objectform">
+<h2>
+[%- IF c.user.username != username -%]Changer le mot de passe de [% username -%]
+[%- ELSE -%]Changer mon mot de passe
+[%- END -%]:
+</h2>
+
+
+[% lastset = user.get_attributes('passwordLastSet') %]
+[% IF lastset %]
+<p>Le mot de passe a Ã©tÃ© changÃ© la derniÃšre fois le [% lastset | html %](GMT)</p>
+[% ELSE %]
+<p>Aucun mot de passe n'a Ã©tÃ© mis pour ce compte</p>
+[% END %]
+
+[% IF c.model('Accounts').db.check_acl(user, 'userPassword', 'w') %]
+
+<form id="fpasswd" action="[% c.uri_for(username, subform) %]" method="POST">
+<table border="1">
+<tr>
+<th>Nouveau mot de passe:</th>
+<td><input type="password" name="passwd" id="passwd"></td>
+</tr>
+<tr>
+<th>Confirmation:</th>
+<td><input type="password" name="cpasswd" id="cpasswd"></td>
+</tr>
+<tr><td colspan="2"><span id="perror">[% pmerror | html %]</span></td></tr>
+</table>
+</form>
+
+<p>Pas d'idÃ©e ?</p>
+<p>Cliquez que gÃ©nÃ©rer pour obtenir un mot alÃ©atoire. Il doit Ãªtre taper et non
+copier/coller. Pensez Ã  le noter.</p>
+
+<form id="fgenpasswd">
+<input type=submit value="GÃ©nÃ©rer !">
+<span id="randpasswd">----</span>
+<ul id="randpasswd_options">
+<li><input type=checkbox name=nonalpha value="1">Utiliser des caractÃšres non
+alpha-numÃ©rique</li>
+<li><input type=checkbox name=syllables value="1">Utiliser des syllabes plutÃŽt que
+des lettres</li>
+</ul>
+</form>
+<script>
+$(function() {
+    $('#fgenpasswd').submit(function() {
+        $.ajax({
+            url: '[% c.uri_for('/ajax', 'random_password') %]',
+            data: $('#fgenpasswd').serialize(),
+    }).done(function(msg) {
+        $('#randpasswd').html(msg);
+    });
+    return false;
+    });
+});
+</script>
+
+[% ELSE %]
+<p>Vous n'avez pas les droits suffisants pour changer ce mot de passe</p>
+[% END %]
+</div>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/d3/LICENSE
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/d3/LICENSE	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/d3/LICENSE	(revision 2119)
@@ -0,0 +1,26 @@
+Copyright (c) 2013, Michael Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* The name Michael Bostock may not be used to endorse or promote products
+  derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/d3/d3.v3.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/d3/d3.v3.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/d3/d3.v3.js	(revision 2119)
@@ -0,0 +1,8795 @@
+d3 = function() {
+  var d3 = {
+    version: "3.2.7"
+  };
+  if (!Date.now) Date.now = function() {
+    return +new Date();
+  };
+  var d3_document = document, d3_documentElement = d3_document.documentElement, d3_window = window;
+  try {
+    d3_document.createElement("div").style.setProperty("opacity", 0, "");
+  } catch (error) {
+    var d3_element_prototype = d3_window.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = d3_window.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;
+    d3_element_prototype.setAttribute = function(name, value) {
+      d3_element_setAttribute.call(this, name, value + "");
+    };
+    d3_element_prototype.setAttributeNS = function(space, local, value) {
+      d3_element_setAttributeNS.call(this, space, local, value + "");
+    };
+    d3_style_prototype.setProperty = function(name, value, priority) {
+      d3_style_setProperty.call(this, name, value + "", priority);
+    };
+  }
+  d3.ascending = function(a, b) {
+    return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
+  };
+  d3.descending = function(a, b) {
+    return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
+  };
+  d3.min = function(array, f) {
+    var i = -1, n = array.length, a, b;
+    if (arguments.length === 1) {
+      while (++i < n && !((a = array[i]) != null && a <= a)) a = undefined;
+      while (++i < n) if ((b = array[i]) != null && a > b) a = b;
+    } else {
+      while (++i < n && !((a = f.call(array, array[i], i)) != null && a <= a)) a = undefined;
+      while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
+    }
+    return a;
+  };
+  d3.max = function(array, f) {
+    var i = -1, n = array.length, a, b;
+    if (arguments.length === 1) {
+      while (++i < n && !((a = array[i]) != null && a <= a)) a = undefined;
+      while (++i < n) if ((b = array[i]) != null && b > a) a = b;
+    } else {
+      while (++i < n && !((a = f.call(array, array[i], i)) != null && a <= a)) a = undefined;
+      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b;
+    }
+    return a;
+  };
+  d3.extent = function(array, f) {
+    var i = -1, n = array.length, a, b, c;
+    if (arguments.length === 1) {
+      while (++i < n && !((a = c = array[i]) != null && a <= a)) a = c = undefined;
+      while (++i < n) if ((b = array[i]) != null) {
+        if (a > b) a = b;
+        if (c < b) c = b;
+      }
+    } else {
+      while (++i < n && !((a = c = f.call(array, array[i], i)) != null && a <= a)) a = undefined;
+      while (++i < n) if ((b = f.call(array, array[i], i)) != null) {
+        if (a > b) a = b;
+        if (c < b) c = b;
+      }
+    }
+    return [ a, c ];
+  };
+  d3.sum = function(array, f) {
+    var s = 0, n = array.length, a, i = -1;
+    if (arguments.length === 1) {
+      while (++i < n) if (!isNaN(a = +array[i])) s += a;
+    } else {
+      while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a;
+    }
+    return s;
+  };
+  function d3_number(x) {
+    return x != null && !isNaN(x);
+  }
+  d3.mean = function(array, f) {
+    var n = array.length, a, m = 0, i = -1, j = 0;
+    if (arguments.length === 1) {
+      while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j;
+    } else {
+      while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j;
+    }
+    return j ? m : undefined;
+  };
+  d3.quantile = function(values, p) {
+    var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h;
+    return e ? v + e * (values[h] - v) : v;
+  };
+  d3.median = function(array, f) {
+    if (arguments.length > 1) array = array.map(f);
+    array = array.filter(d3_number);
+    return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined;
+  };
+  d3.bisector = function(f) {
+    return {
+      left: function(a, x, lo, hi) {
+        if (arguments.length < 3) lo = 0;
+        if (arguments.length < 4) hi = a.length;
+        while (lo < hi) {
+          var mid = lo + hi >>> 1;
+          if (f.call(a, a[mid], mid) < x) lo = mid + 1; else hi = mid;
+        }
+        return lo;
+      },
+      right: function(a, x, lo, hi) {
+        if (arguments.length < 3) lo = 0;
+        if (arguments.length < 4) hi = a.length;
+        while (lo < hi) {
+          var mid = lo + hi >>> 1;
+          if (x < f.call(a, a[mid], mid)) hi = mid; else lo = mid + 1;
+        }
+        return lo;
+      }
+    };
+  };
+  var d3_bisector = d3.bisector(function(d) {
+    return d;
+  });
+  d3.bisectLeft = d3_bisector.left;
+  d3.bisect = d3.bisectRight = d3_bisector.right;
+  d3.shuffle = function(array) {
+    var m = array.length, t, i;
+    while (m) {
+      i = Math.random() * m-- | 0;
+      t = array[m], array[m] = array[i], array[i] = t;
+    }
+    return array;
+  };
+  d3.permute = function(array, indexes) {
+    var permutes = [], i = -1, n = indexes.length;
+    while (++i < n) permutes[i] = array[indexes[i]];
+    return permutes;
+  };
+  d3.zip = function() {
+    if (!(n = arguments.length)) return [];
+    for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) {
+      for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) {
+        zip[j] = arguments[j][i];
+      }
+    }
+    return zips;
+  };
+  function d3_zipLength(d) {
+    return d.length;
+  }
+  d3.transpose = function(matrix) {
+    return d3.zip.apply(d3, matrix);
+  };
+  d3.keys = function(map) {
+    var keys = [];
+    for (var key in map) keys.push(key);
+    return keys;
+  };
+  d3.values = function(map) {
+    var values = [];
+    for (var key in map) values.push(map[key]);
+    return values;
+  };
+  d3.entries = function(map) {
+    var entries = [];
+    for (var key in map) entries.push({
+      key: key,
+      value: map[key]
+    });
+    return entries;
+  };
+  d3.merge = function(arrays) {
+    return Array.prototype.concat.apply([], arrays);
+  };
+  d3.range = function(start, stop, step) {
+    if (arguments.length < 3) {
+      step = 1;
+      if (arguments.length < 2) {
+        stop = start;
+        start = 0;
+      }
+    }
+    if ((stop - start) / step === Infinity) throw new Error("infinite range");
+    var range = [], k = d3_range_integerScale(Math.abs(step)), i = -1, j;
+    start *= k, stop *= k, step *= k;
+    if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k);
+    return range;
+  };
+  function d3_range_integerScale(x) {
+    var k = 1;
+    while (x * k % 1) k *= 10;
+    return k;
+  }
+  function d3_class(ctor, properties) {
+    try {
+      for (var key in properties) {
+        Object.defineProperty(ctor.prototype, key, {
+          value: properties[key],
+          enumerable: false
+        });
+      }
+    } catch (e) {
+      ctor.prototype = properties;
+    }
+  }
+  d3.map = function(object) {
+    var map = new d3_Map();
+    for (var key in object) map.set(key, object[key]);
+    return map;
+  };
+  function d3_Map() {}
+  d3_class(d3_Map, {
+    has: function(key) {
+      return d3_map_prefix + key in this;
+    },
+    get: function(key) {
+      return this[d3_map_prefix + key];
+    },
+    set: function(key, value) {
+      return this[d3_map_prefix + key] = value;
+    },
+    remove: function(key) {
+      key = d3_map_prefix + key;
+      return key in this && delete this[key];
+    },
+    keys: function() {
+      var keys = [];
+      this.forEach(function(key) {
+        keys.push(key);
+      });
+      return keys;
+    },
+    values: function() {
+      var values = [];
+      this.forEach(function(key, value) {
+        values.push(value);
+      });
+      return values;
+    },
+    entries: function() {
+      var entries = [];
+      this.forEach(function(key, value) {
+        entries.push({
+          key: key,
+          value: value
+        });
+      });
+      return entries;
+    },
+    forEach: function(f) {
+      for (var key in this) {
+        if (key.charCodeAt(0) === d3_map_prefixCode) {
+          f.call(this, key.substring(1), this[key]);
+        }
+      }
+    }
+  });
+  var d3_map_prefix = "\0", d3_map_prefixCode = d3_map_prefix.charCodeAt(0);
+  d3.nest = function() {
+    var nest = {}, keys = [], sortKeys = [], sortValues, rollup;
+    function map(mapType, array, depth) {
+      if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array;
+      var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values;
+      while (++i < n) {
+        if (values = valuesByKey.get(keyValue = key(object = array[i]))) {
+          values.push(object);
+        } else {
+          valuesByKey.set(keyValue, [ object ]);
+        }
+      }
+      if (mapType) {
+        object = mapType();
+        setter = function(keyValue, values) {
+          object.set(keyValue, map(mapType, values, depth));
+        };
+      } else {
+        object = {};
+        setter = function(keyValue, values) {
+          object[keyValue] = map(mapType, values, depth);
+        };
+      }
+      valuesByKey.forEach(setter);
+      return object;
+    }
+    function entries(map, depth) {
+      if (depth >= keys.length) return map;
+      var array = [], sortKey = sortKeys[depth++];
+      map.forEach(function(key, keyMap) {
+        array.push({
+          key: key,
+          values: entries(keyMap, depth)
+        });
+      });
+      return sortKey ? array.sort(function(a, b) {
+        return sortKey(a.key, b.key);
+      }) : array;
+    }
+    nest.map = function(array, mapType) {
+      return map(mapType, array, 0);
+    };
+    nest.entries = function(array) {
+      return entries(map(d3.map, array, 0), 0);
+    };
+    nest.key = function(d) {
+      keys.push(d);
+      return nest;
+    };
+    nest.sortKeys = function(order) {
+      sortKeys[keys.length - 1] = order;
+      return nest;
+    };
+    nest.sortValues = function(order) {
+      sortValues = order;
+      return nest;
+    };
+    nest.rollup = function(f) {
+      rollup = f;
+      return nest;
+    };
+    return nest;
+  };
+  d3.set = function(array) {
+    var set = new d3_Set();
+    if (array) for (var i = 0; i < array.length; i++) set.add(array[i]);
+    return set;
+  };
+  function d3_Set() {}
+  d3_class(d3_Set, {
+    has: function(value) {
+      return d3_map_prefix + value in this;
+    },
+    add: function(value) {
+      this[d3_map_prefix + value] = true;
+      return value;
+    },
+    remove: function(value) {
+      value = d3_map_prefix + value;
+      return value in this && delete this[value];
+    },
+    values: function() {
+      var values = [];
+      this.forEach(function(value) {
+        values.push(value);
+      });
+      return values;
+    },
+    forEach: function(f) {
+      for (var value in this) {
+        if (value.charCodeAt(0) === d3_map_prefixCode) {
+          f.call(this, value.substring(1));
+        }
+      }
+    }
+  });
+  d3.behavior = {};
+  d3.rebind = function(target, source) {
+    var i = 1, n = arguments.length, method;
+    while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
+    return target;
+  };
+  function d3_rebind(target, source, method) {
+    return function() {
+      var value = method.apply(source, arguments);
+      return value === source ? target : value;
+    };
+  }
+  function d3_vendorSymbol(object, name) {
+    if (name in object) return name;
+    name = name.charAt(0).toUpperCase() + name.substring(1);
+    for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) {
+      var prefixName = d3_vendorPrefixes[i] + name;
+      if (prefixName in object) return prefixName;
+    }
+  }
+  var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ];
+  var d3_array = d3_arraySlice;
+  function d3_arrayCopy(pseudoarray) {
+    var i = -1, n = pseudoarray.length, array = [];
+    while (++i < n) array.push(pseudoarray[i]);
+    return array;
+  }
+  function d3_arraySlice(pseudoarray) {
+    return Array.prototype.slice.call(pseudoarray);
+  }
+  try {
+    d3_array(d3_documentElement.childNodes)[0].nodeType;
+  } catch (e) {
+    d3_array = d3_arrayCopy;
+  }
+  function d3_noop() {}
+  d3.dispatch = function() {
+    var dispatch = new d3_dispatch(), i = -1, n = arguments.length;
+    while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
+    return dispatch;
+  };
+  function d3_dispatch() {}
+  d3_dispatch.prototype.on = function(type, listener) {
+    var i = type.indexOf("."), name = "";
+    if (i >= 0) {
+      name = type.substring(i + 1);
+      type = type.substring(0, i);
+    }
+    if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener);
+    if (arguments.length === 2) {
+      if (listener == null) for (type in this) {
+        if (this.hasOwnProperty(type)) this[type].on(name, null);
+      }
+      return this;
+    }
+  };
+  function d3_dispatch_event(dispatch) {
+    var listeners = [], listenerByName = new d3_Map();
+    function event() {
+      var z = listeners, i = -1, n = z.length, l;
+      while (++i < n) if (l = z[i].on) l.apply(this, arguments);
+      return dispatch;
+    }
+    event.on = function(name, listener) {
+      var l = listenerByName.get(name), i;
+      if (arguments.length < 2) return l && l.on;
+      if (l) {
+        l.on = null;
+        listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
+        listenerByName.remove(name);
+      }
+      if (listener) listeners.push(listenerByName.set(name, {
+        on: listener
+      }));
+      return dispatch;
+    };
+    return event;
+  }
+  d3.event = null;
+  function d3_eventPreventDefault() {
+    d3.event.preventDefault();
+  }
+  function d3_eventSource() {
+    var e = d3.event, s;
+    while (s = e.sourceEvent) e = s;
+    return e;
+  }
+  function d3_eventDispatch(target) {
+    var dispatch = new d3_dispatch(), i = 0, n = arguments.length;
+    while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
+    dispatch.of = function(thiz, argumentz) {
+      return function(e1) {
+        try {
+          var e0 = e1.sourceEvent = d3.event;
+          e1.target = target;
+          d3.event = e1;
+          dispatch[e1.type].apply(thiz, argumentz);
+        } finally {
+          d3.event = e0;
+        }
+      };
+    };
+    return dispatch;
+  }
+  d3.requote = function(s) {
+    return s.replace(d3_requote_re, "\\$&");
+  };
+  var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
+  var d3_subclass = {}.__proto__ ? function(object, prototype) {
+    object.__proto__ = prototype;
+  } : function(object, prototype) {
+    for (var property in prototype) object[property] = prototype[property];
+  };
+  function d3_selection(groups) {
+    d3_subclass(groups, d3_selectionPrototype);
+    return groups;
+  }
+  var d3_select = function(s, n) {
+    return n.querySelector(s);
+  }, d3_selectAll = function(s, n) {
+    return n.querySelectorAll(s);
+  }, d3_selectMatcher = d3_documentElement[d3_vendorSymbol(d3_documentElement, "matchesSelector")], d3_selectMatches = function(n, s) {
+    return d3_selectMatcher.call(n, s);
+  };
+  if (typeof Sizzle === "function") {
+    d3_select = function(s, n) {
+      return Sizzle(s, n)[0] || null;
+    };
+    d3_selectAll = function(s, n) {
+      return Sizzle.uniqueSort(Sizzle(s, n));
+    };
+    d3_selectMatches = Sizzle.matchesSelector;
+  }
+  d3.selection = function() {
+    return d3_selectionRoot;
+  };
+  var d3_selectionPrototype = d3.selection.prototype = [];
+  d3_selectionPrototype.select = function(selector) {
+    var subgroups = [], subgroup, subnode, group, node;
+    selector = d3_selection_selector(selector);
+    for (var j = -1, m = this.length; ++j < m; ) {
+      subgroups.push(subgroup = []);
+      subgroup.parentNode = (group = this[j]).parentNode;
+      for (var i = -1, n = group.length; ++i < n; ) {
+        if (node = group[i]) {
+          subgroup.push(subnode = selector.call(node, node.__data__, i, j));
+          if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
+        } else {
+          subgroup.push(null);
+        }
+      }
+    }
+    return d3_selection(subgroups);
+  };
+  function d3_selection_selector(selector) {
+    return typeof selector === "function" ? selector : function() {
+      return d3_select(selector, this);
+    };
+  }
+  d3_selectionPrototype.selectAll = function(selector) {
+    var subgroups = [], subgroup, node;
+    selector = d3_selection_selectorAll(selector);
+    for (var j = -1, m = this.length; ++j < m; ) {
+      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+        if (node = group[i]) {
+          subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j)));
+          subgroup.parentNode = node;
+        }
+      }
+    }
+    return d3_selection(subgroups);
+  };
+  function d3_selection_selectorAll(selector) {
+    return typeof selector === "function" ? selector : function() {
+      return d3_selectAll(selector, this);
+    };
+  }
+  var d3_nsPrefix = {
+    svg: "http://www.w3.org/2000/svg",
+    xhtml: "http://www.w3.org/1999/xhtml",
+    xlink: "http://www.w3.org/1999/xlink",
+    xml: "http://www.w3.org/XML/1998/namespace",
+    xmlns: "http://www.w3.org/2000/xmlns/"
+  };
+  d3.ns = {
+    prefix: d3_nsPrefix,
+    qualify: function(name) {
+      var i = name.indexOf(":"), prefix = name;
+      if (i >= 0) {
+        prefix = name.substring(0, i);
+        name = name.substring(i + 1);
+      }
+      return d3_nsPrefix.hasOwnProperty(prefix) ? {
+        space: d3_nsPrefix[prefix],
+        local: name
+      } : name;
+    }
+  };
+  d3_selectionPrototype.attr = function(name, value) {
+    if (arguments.length < 2) {
+      if (typeof name === "string") {
+        var node = this.node();
+        name = d3.ns.qualify(name);
+        return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name);
+      }
+      for (value in name) this.each(d3_selection_attr(value, name[value]));
+      return this;
+    }
+    return this.each(d3_selection_attr(name, value));
+  };
+  function d3_selection_attr(name, value) {
+    name = d3.ns.qualify(name);
+    function attrNull() {
+      this.removeAttribute(name);
+    }
+    function attrNullNS() {
+      this.removeAttributeNS(name.space, name.local);
+    }
+    function attrConstant() {
+      this.setAttribute(name, value);
+    }
+    function attrConstantNS() {
+      this.setAttributeNS(name.space, name.local, value);
+    }
+    function attrFunction() {
+      var x = value.apply(this, arguments);
+      if (x == null) this.removeAttribute(name); else this.setAttribute(name, x);
+    }
+    function attrFunctionNS() {
+      var x = value.apply(this, arguments);
+      if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x);
+    }
+    return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant;
+  }
+  function d3_collapse(s) {
+    return s.trim().replace(/\s+/g, " ");
+  }
+  d3_selectionPrototype.classed = function(name, value) {
+    if (arguments.length < 2) {
+      if (typeof name === "string") {
+        var node = this.node(), n = (name = name.trim().split(/^|\s+/g)).length, i = -1;
+        if (value = node.classList) {
+          while (++i < n) if (!value.contains(name[i])) return false;
+        } else {
+          value = node.getAttribute("class");
+          while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
+        }
+        return true;
+      }
+      for (value in name) this.each(d3_selection_classed(value, name[value]));
+      return this;
+    }
+    return this.each(d3_selection_classed(name, value));
+  };
+  function d3_selection_classedRe(name) {
+    return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
+  }
+  function d3_selection_classed(name, value) {
+    name = name.trim().split(/\s+/).map(d3_selection_classedName);
+    var n = name.length;
+    function classedConstant() {
+      var i = -1;
+      while (++i < n) name[i](this, value);
+    }
+    function classedFunction() {
+      var i = -1, x = value.apply(this, arguments);
+      while (++i < n) name[i](this, x);
+    }
+    return typeof value === "function" ? classedFunction : classedConstant;
+  }
+  function d3_selection_classedName(name) {
+    var re = d3_selection_classedRe(name);
+    return function(node, value) {
+      if (c = node.classList) return value ? c.add(name) : c.remove(name);
+      var c = node.getAttribute("class") || "";
+      if (value) {
+        re.lastIndex = 0;
+        if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name));
+      } else {
+        node.setAttribute("class", d3_collapse(c.replace(re, " ")));
+      }
+    };
+  }
+  d3_selectionPrototype.style = function(name, value, priority) {
+    var n = arguments.length;
+    if (n < 3) {
+      if (typeof name !== "string") {
+        if (n < 2) value = "";
+        for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
+        return this;
+      }
+      if (n < 2) return d3_window.getComputedStyle(this.node(), null).getPropertyValue(name);
+      priority = "";
+    }
+    return this.each(d3_selection_style(name, value, priority));
+  };
+  function d3_selection_style(name, value, priority) {
+    function styleNull() {
+      this.style.removeProperty(name);
+    }
+    function styleConstant() {
+      this.style.setProperty(name, value, priority);
+    }
+    function styleFunction() {
+      var x = value.apply(this, arguments);
+      if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority);
+    }
+    return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant;
+  }
+  d3_selectionPrototype.property = function(name, value) {
+    if (arguments.length < 2) {
+      if (typeof name === "string") return this.node()[name];
+      for (value in name) this.each(d3_selection_property(value, name[value]));
+      return this;
+    }
+    return this.each(d3_selection_property(name, value));
+  };
+  function d3_selection_property(name, value) {
+    function propertyNull() {
+      delete this[name];
+    }
+    function propertyConstant() {
+      this[name] = value;
+    }
+    function propertyFunction() {
+      var x = value.apply(this, arguments);
+      if (x == null) delete this[name]; else this[name] = x;
+    }
+    return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant;
+  }
+  d3_selectionPrototype.text = function(value) {
+    return arguments.length ? this.each(typeof value === "function" ? function() {
+      var v = value.apply(this, arguments);
+      this.textContent = v == null ? "" : v;
+    } : value == null ? function() {
+      this.textContent = "";
+    } : function() {
+      this.textContent = value;
+    }) : this.node().textContent;
+  };
+  d3_selectionPrototype.html = function(value) {
+    return arguments.length ? this.each(typeof value === "function" ? function() {
+      var v = value.apply(this, arguments);
+      this.innerHTML = v == null ? "" : v;
+    } : value == null ? function() {
+      this.innerHTML = "";
+    } : function() {
+      this.innerHTML = value;
+    }) : this.node().innerHTML;
+  };
+  d3_selectionPrototype.append = function(name) {
+    name = d3_selection_creator(name);
+    return this.select(function() {
+      return this.appendChild(name.apply(this, arguments));
+    });
+  };
+  function d3_selection_creator(name) {
+    return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? function() {
+      return d3_document.createElementNS(name.space, name.local);
+    } : function() {
+      return d3_document.createElementNS(this.namespaceURI, name);
+    };
+  }
+  d3_selectionPrototype.insert = function(name, before) {
+    name = d3_selection_creator(name);
+    before = d3_selection_selector(before);
+    return this.select(function() {
+      return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments));
+    });
+  };
+  d3_selectionPrototype.remove = function() {
+    return this.each(function() {
+      var parent = this.parentNode;
+      if (parent) parent.removeChild(this);
+    });
+  };
+  d3_selectionPrototype.data = function(value, key) {
+    var i = -1, n = this.length, group, node;
+    if (!arguments.length) {
+      value = new Array(n = (group = this[0]).length);
+      while (++i < n) {
+        if (node = group[i]) {
+          value[i] = node.__data__;
+        }
+      }
+      return value;
+    }
+    function bind(group, groupData) {
+      var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData;
+      if (key) {
+        var nodeByKeyValue = new d3_Map(), dataByKeyValue = new d3_Map(), keyValues = [], keyValue;
+        for (i = -1; ++i < n; ) {
+          keyValue = key.call(node = group[i], node.__data__, i);
+          if (nodeByKeyValue.has(keyValue)) {
+            exitNodes[i] = node;
+          } else {
+            nodeByKeyValue.set(keyValue, node);
+          }
+          keyValues.push(keyValue);
+        }
+        for (i = -1; ++i < m; ) {
+          keyValue = key.call(groupData, nodeData = groupData[i], i);
+          if (node = nodeByKeyValue.get(keyValue)) {
+            updateNodes[i] = node;
+            node.__data__ = nodeData;
+          } else if (!dataByKeyValue.has(keyValue)) {
+            enterNodes[i] = d3_selection_dataNode(nodeData);
+          }
+          dataByKeyValue.set(keyValue, nodeData);
+          nodeByKeyValue.remove(keyValue);
+        }
+        for (i = -1; ++i < n; ) {
+          if (nodeByKeyValue.has(keyValues[i])) {
+            exitNodes[i] = group[i];
+          }
+        }
+      } else {
+        for (i = -1; ++i < n0; ) {
+          node = group[i];
+          nodeData = groupData[i];
+          if (node) {
+            node.__data__ = nodeData;
+            updateNodes[i] = node;
+          } else {
+            enterNodes[i] = d3_selection_dataNode(nodeData);
+          }
+        }
+        for (;i < m; ++i) {
+          enterNodes[i] = d3_selection_dataNode(groupData[i]);
+        }
+        for (;i < n; ++i) {
+          exitNodes[i] = group[i];
+        }
+      }
+      enterNodes.update = updateNodes;
+      enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode;
+      enter.push(enterNodes);
+      update.push(updateNodes);
+      exit.push(exitNodes);
+    }
+    var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]);
+    if (typeof value === "function") {
+      while (++i < n) {
+        bind(group = this[i], value.call(group, group.parentNode.__data__, i));
+      }
+    } else {
+      while (++i < n) {
+        bind(group = this[i], value);
+      }
+    }
+    update.enter = function() {
+      return enter;
+    };
+    update.exit = function() {
+      return exit;
+    };
+    return update;
+  };
+  function d3_selection_dataNode(data) {
+    return {
+      __data__: data
+    };
+  }
+  d3_selectionPrototype.datum = function(value) {
+    return arguments.length ? this.property("__data__", value) : this.property("__data__");
+  };
+  d3_selectionPrototype.filter = function(filter) {
+    var subgroups = [], subgroup, group, node;
+    if (typeof filter !== "function") filter = d3_selection_filter(filter);
+    for (var j = 0, m = this.length; j < m; j++) {
+      subgroups.push(subgroup = []);
+      subgroup.parentNode = (group = this[j]).parentNode;
+      for (var i = 0, n = group.length; i < n; i++) {
+        if ((node = group[i]) && filter.call(node, node.__data__, i)) {
+          subgroup.push(node);
+        }
+      }
+    }
+    return d3_selection(subgroups);
+  };
+  function d3_selection_filter(selector) {
+    return function() {
+      return d3_selectMatches(this, selector);
+    };
+  }
+  d3_selectionPrototype.order = function() {
+    for (var j = -1, m = this.length; ++j < m; ) {
+      for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) {
+        if (node = group[i]) {
+          if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
+          next = node;
+        }
+      }
+    }
+    return this;
+  };
+  d3_selectionPrototype.sort = function(comparator) {
+    comparator = d3_selection_sortComparator.apply(this, arguments);
+    for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator);
+    return this.order();
+  };
+  function d3_selection_sortComparator(comparator) {
+    if (!arguments.length) comparator = d3.ascending;
+    return function(a, b) {
+      return !a - !b || comparator(a.__data__, b.__data__);
+    };
+  }
+  d3_selectionPrototype.each = function(callback) {
+    return d3_selection_each(this, function(node, i, j) {
+      callback.call(node, node.__data__, i, j);
+    });
+  };
+  function d3_selection_each(groups, callback) {
+    for (var j = 0, m = groups.length; j < m; j++) {
+      for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) {
+        if (node = group[i]) callback(node, i, j);
+      }
+    }
+    return groups;
+  }
+  d3_selectionPrototype.call = function(callback) {
+    var args = d3_array(arguments);
+    callback.apply(args[0] = this, args);
+    return this;
+  };
+  d3_selectionPrototype.empty = function() {
+    return !this.node();
+  };
+  d3_selectionPrototype.node = function() {
+    for (var j = 0, m = this.length; j < m; j++) {
+      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+        var node = group[i];
+        if (node) return node;
+      }
+    }
+    return null;
+  };
+  d3_selectionPrototype.size = function() {
+    var n = 0;
+    this.each(function() {
+      ++n;
+    });
+    return n;
+  };
+  function d3_selection_enter(selection) {
+    d3_subclass(selection, d3_selection_enterPrototype);
+    return selection;
+  }
+  var d3_selection_enterPrototype = [];
+  d3.selection.enter = d3_selection_enter;
+  d3.selection.enter.prototype = d3_selection_enterPrototype;
+  d3_selection_enterPrototype.append = d3_selectionPrototype.append;
+  d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
+  d3_selection_enterPrototype.node = d3_selectionPrototype.node;
+  d3_selection_enterPrototype.call = d3_selectionPrototype.call;
+  d3_selection_enterPrototype.size = d3_selectionPrototype.size;
+  d3_selection_enterPrototype.select = function(selector) {
+    var subgroups = [], subgroup, subnode, upgroup, group, node;
+    for (var j = -1, m = this.length; ++j < m; ) {
+      upgroup = (group = this[j]).update;
+      subgroups.push(subgroup = []);
+      subgroup.parentNode = group.parentNode;
+      for (var i = -1, n = group.length; ++i < n; ) {
+        if (node = group[i]) {
+          subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j));
+          subnode.__data__ = node.__data__;
+        } else {
+          subgroup.push(null);
+        }
+      }
+    }
+    return d3_selection(subgroups);
+  };
+  d3_selection_enterPrototype.insert = function(name, before) {
+    if (arguments.length < 2) before = d3_selection_enterInsertBefore(this);
+    return d3_selectionPrototype.insert.call(this, name, before);
+  };
+  function d3_selection_enterInsertBefore(enter) {
+    var i0, j0;
+    return function(d, i, j) {
+      var group = enter[j].update, n = group.length, node;
+      if (j != j0) j0 = j, i0 = 0;
+      if (i >= i0) i0 = i + 1;
+      while (!(node = group[i0]) && ++i0 < n) ;
+      return node;
+    };
+  }
+  d3_selectionPrototype.transition = function() {
+    var id = d3_transitionInheritId || ++d3_transitionId, subgroups = [], subgroup, node, transition = d3_transitionInherit || {
+      time: Date.now(),
+      ease: d3_ease_cubicInOut,
+      delay: 0,
+      duration: 250
+    };
+    for (var j = -1, m = this.length; ++j < m; ) {
+      subgroups.push(subgroup = []);
+      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+        if (node = group[i]) d3_transitionNode(node, i, id, transition);
+        subgroup.push(node);
+      }
+    }
+    return d3_transition(subgroups, id);
+  };
+  d3.select = function(node) {
+    var group = [ typeof node === "string" ? d3_select(node, d3_document) : node ];
+    group.parentNode = d3_documentElement;
+    return d3_selection([ group ]);
+  };
+  d3.selectAll = function(nodes) {
+    var group = d3_array(typeof nodes === "string" ? d3_selectAll(nodes, d3_document) : nodes);
+    group.parentNode = d3_documentElement;
+    return d3_selection([ group ]);
+  };
+  var d3_selectionRoot = d3.select(d3_documentElement);
+  d3_selectionPrototype.on = function(type, listener, capture) {
+    var n = arguments.length;
+    if (n < 3) {
+      if (typeof type !== "string") {
+        if (n < 2) listener = false;
+        for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
+        return this;
+      }
+      if (n < 2) return (n = this.node()["__on" + type]) && n._;
+      capture = false;
+    }
+    return this.each(d3_selection_on(type, listener, capture));
+  };
+  function d3_selection_on(type, listener, capture) {
+    var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener;
+    if (i > 0) type = type.substring(0, i);
+    var filter = d3_selection_onFilters.get(type);
+    if (filter) type = filter, wrap = d3_selection_onFilter;
+    function onRemove() {
+      var l = this[name];
+      if (l) {
+        this.removeEventListener(type, l, l.$);
+        delete this[name];
+      }
+    }
+    function onAdd() {
+      var l = wrap(listener, d3_array(arguments));
+      onRemove.call(this);
+      this.addEventListener(type, this[name] = l, l.$ = capture);
+      l._ = listener;
+    }
+    function removeAll() {
+      var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match;
+      for (var name in this) {
+        if (match = name.match(re)) {
+          var l = this[name];
+          this.removeEventListener(match[1], l, l.$);
+          delete this[name];
+        }
+      }
+    }
+    return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll;
+  }
+  var d3_selection_onFilters = d3.map({
+    mouseenter: "mouseover",
+    mouseleave: "mouseout"
+  });
+  d3_selection_onFilters.forEach(function(k) {
+    if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
+  });
+  function d3_selection_onListener(listener, argumentz) {
+    return function(e) {
+      var o = d3.event;
+      d3.event = e;
+      argumentz[0] = this.__data__;
+      try {
+        listener.apply(this, argumentz);
+      } finally {
+        d3.event = o;
+      }
+    };
+  }
+  function d3_selection_onFilter(listener, argumentz) {
+    var l = d3_selection_onListener(listener, argumentz);
+    return function(e) {
+      var target = this, related = e.relatedTarget;
+      if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) {
+        l.call(target, e);
+      }
+    };
+  }
+  var d3_event_dragSelect = d3_vendorSymbol(d3_documentElement.style, "userSelect"), d3_event_dragId = 0;
+  function d3_event_dragSuppress() {
+    var name = ".dragsuppress-" + ++d3_event_dragId, touchmove = "touchmove" + name, selectstart = "selectstart" + name, dragstart = "dragstart" + name, click = "click" + name, w = d3.select(d3_window).on(touchmove, d3_eventPreventDefault).on(selectstart, d3_eventPreventDefault).on(dragstart, d3_eventPreventDefault), style = d3_documentElement.style, select = style[d3_event_dragSelect];
+    style[d3_event_dragSelect] = "none";
+    return function(suppressClick) {
+      w.on(name, null);
+      style[d3_event_dragSelect] = select;
+      if (suppressClick) {
+        function off() {
+          w.on(click, null);
+        }
+        w.on(click, function() {
+          d3_eventPreventDefault();
+          off();
+        }, true);
+        setTimeout(off, 0);
+      }
+    };
+  }
+  d3.mouse = function(container) {
+    return d3_mousePoint(container, d3_eventSource());
+  };
+  var d3_mouse_bug44083 = /WebKit/.test(d3_window.navigator.userAgent) ? -1 : 0;
+  function d3_mousePoint(container, e) {
+    var svg = container.ownerSVGElement || container;
+    if (svg.createSVGPoint) {
+      var point = svg.createSVGPoint();
+      if (d3_mouse_bug44083 < 0 && (d3_window.scrollX || d3_window.scrollY)) {
+        svg = d3.select("body").append("svg").style({
+          position: "absolute",
+          top: 0,
+          left: 0,
+          margin: 0,
+          padding: 0,
+          border: "none"
+        }, "important");
+        var ctm = svg[0][0].getScreenCTM();
+        d3_mouse_bug44083 = !(ctm.f || ctm.e);
+        svg.remove();
+      }
+      if (d3_mouse_bug44083) {
+        point.x = e.pageX;
+        point.y = e.pageY;
+      } else {
+        point.x = e.clientX;
+        point.y = e.clientY;
+      }
+      point = point.matrixTransform(container.getScreenCTM().inverse());
+      return [ point.x, point.y ];
+    }
+    var rect = container.getBoundingClientRect();
+    return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ];
+  }
+  d3.touches = function(container, touches) {
+    if (arguments.length < 2) touches = d3_eventSource().touches;
+    return touches ? d3_array(touches).map(function(touch) {
+      var point = d3_mousePoint(container, touch);
+      point.identifier = touch.identifier;
+      return point;
+    }) : [];
+  };
+  d3.behavior.drag = function() {
+    var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, "mousemove", "mouseup"), touchstart = dragstart(touchid, touchposition, "touchmove", "touchend");
+    function drag() {
+      this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart);
+    }
+    function touchid() {
+      return d3.event.changedTouches[0].identifier;
+    }
+    function touchposition(parent, id) {
+      return d3.touches(parent).filter(function(p) {
+        return p.identifier === id;
+      })[0];
+    }
+    function dragstart(id, position, move, end) {
+      return function() {
+        var target = this, parent = target.parentNode, event_ = event.of(target, arguments), eventTarget = d3.event.target, eventId = id(), drag = eventId == null ? "drag" : "drag-" + eventId, origin_ = position(parent, eventId), dragged = 0, offset, w = d3.select(d3_window).on(move + "." + drag, moved).on(end + "." + drag, ended), dragRestore = d3_event_dragSuppress();
+        if (origin) {
+          offset = origin.apply(target, arguments);
+          offset = [ offset.x - origin_[0], offset.y - origin_[1] ];
+        } else {
+          offset = [ 0, 0 ];
+        }
+        event_({
+          type: "dragstart"
+        });
+        function moved() {
+          if (!parent) return ended();
+          var p = position(parent, eventId), dx = p[0] - origin_[0], dy = p[1] - origin_[1];
+          dragged |= dx | dy;
+          origin_ = p;
+          event_({
+            type: "drag",
+            x: p[0] + offset[0],
+            y: p[1] + offset[1],
+            dx: dx,
+            dy: dy
+          });
+        }
+        function ended() {
+          w.on(move + "." + drag, null).on(end + "." + drag, null);
+          dragRestore(dragged && d3.event.target === eventTarget);
+          event_({
+            type: "dragend"
+          });
+        }
+      };
+    }
+    drag.origin = function(x) {
+      if (!arguments.length) return origin;
+      origin = x;
+      return drag;
+    };
+    return d3.rebind(drag, event, "on");
+  };
+  d3.behavior.zoom = function() {
+    var translate = [ 0, 0 ], translate0, scale = 1, scaleExtent = d3_behavior_zoomInfinity, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", event = d3_eventDispatch(zoom, "zoom"), x0, x1, y0, y1, touchtime;
+    function zoom() {
+      this.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on(mousemove, mousewheelreset).on("dblclick.zoom", dblclicked).on("touchstart.zoom", touchstarted);
+    }
+    zoom.translate = function(x) {
+      if (!arguments.length) return translate;
+      translate = x.map(Number);
+      rescale();
+      return zoom;
+    };
+    zoom.scale = function(x) {
+      if (!arguments.length) return scale;
+      scale = +x;
+      rescale();
+      return zoom;
+    };
+    zoom.scaleExtent = function(x) {
+      if (!arguments.length) return scaleExtent;
+      scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number);
+      return zoom;
+    };
+    zoom.x = function(z) {
+      if (!arguments.length) return x1;
+      x1 = z;
+      x0 = z.copy();
+      translate = [ 0, 0 ];
+      scale = 1;
+      return zoom;
+    };
+    zoom.y = function(z) {
+      if (!arguments.length) return y1;
+      y1 = z;
+      y0 = z.copy();
+      translate = [ 0, 0 ];
+      scale = 1;
+      return zoom;
+    };
+    function location(p) {
+      return [ (p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale ];
+    }
+    function point(l) {
+      return [ l[0] * scale + translate[0], l[1] * scale + translate[1] ];
+    }
+    function scaleTo(s) {
+      scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
+    }
+    function translateTo(p, l) {
+      l = point(l);
+      translate[0] += p[0] - l[0];
+      translate[1] += p[1] - l[1];
+    }
+    function rescale() {
+      if (x1) x1.domain(x0.range().map(function(x) {
+        return (x - translate[0]) / scale;
+      }).map(x0.invert));
+      if (y1) y1.domain(y0.range().map(function(y) {
+        return (y - translate[1]) / scale;
+      }).map(y0.invert));
+    }
+    function dispatch(event) {
+      rescale();
+      event({
+        type: "zoom",
+        scale: scale,
+        translate: translate
+      });
+    }
+    function mousedowned() {
+      var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, dragged = 0, w = d3.select(d3_window).on(mousemove, moved).on(mouseup, ended), l = location(d3.mouse(target)), dragRestore = d3_event_dragSuppress();
+      function moved() {
+        dragged = 1;
+        translateTo(d3.mouse(target), l);
+        dispatch(event_);
+      }
+      function ended() {
+        w.on(mousemove, d3_window === target ? mousewheelreset : null).on(mouseup, null);
+        dragRestore(dragged && d3.event.target === eventTarget);
+      }
+    }
+    function touchstarted() {
+      var target = this, event_ = event.of(target, arguments), touches = d3.touches(target), locations = {}, distance0 = 0, scale0 = scale, now = Date.now(), name = "zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove." + name, touchend = "touchend." + name, w = d3.select(d3_window).on(touchmove, moved).on(touchend, ended), t = d3.select(target).on(mousedown, null), dragRestore = d3_event_dragSuppress();
+      touches.forEach(function(t) {
+        locations[t.identifier] = location(t);
+      });
+      if (touches.length === 1) {
+        if (now - touchtime < 500) {
+          var p = touches[0], l = location(touches[0]);
+          scaleTo(scale * 2);
+          translateTo(p, l);
+          d3_eventPreventDefault();
+          dispatch(event_);
+        }
+        touchtime = now;
+      } else if (touches.length > 1) {
+        var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1];
+        distance0 = dx * dx + dy * dy;
+      }
+      function moved() {
+        var touches = d3.touches(target), p0 = touches[0], l0 = locations[p0.identifier];
+        if (p1 = touches[1]) {
+          var p1, l1 = locations[p1.identifier], scale1 = d3.event.scale;
+          if (scale1 == null) {
+            var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1;
+            scale1 = distance0 && Math.sqrt(distance1 / distance0);
+          }
+          p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ];
+          l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ];
+          scaleTo(scale1 * scale0);
+        }
+        touchtime = null;
+        translateTo(p0, l0);
+        dispatch(event_);
+      }
+      function ended() {
+        w.on(touchmove, null).on(touchend, null);
+        t.on(mousedown, mousedowned);
+        dragRestore();
+      }
+    }
+    function mousewheeled() {
+      d3_eventPreventDefault();
+      if (!translate0) translate0 = location(d3.mouse(this));
+      scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale);
+      translateTo(d3.mouse(this), translate0);
+      dispatch(event.of(this, arguments));
+    }
+    function mousewheelreset() {
+      translate0 = null;
+    }
+    function dblclicked() {
+      var p = d3.mouse(this), l = location(p), k = Math.log(scale) / Math.LN2;
+      scaleTo(Math.pow(2, d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1));
+      translateTo(p, l);
+      dispatch(event.of(this, arguments));
+    }
+    return d3.rebind(zoom, event, "on");
+  };
+  var d3_behavior_zoomInfinity = [ 0, Infinity ];
+  var d3_behavior_zoomDelta, d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() {
+    return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1);
+  }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() {
+    return d3.event.wheelDelta;
+  }, "mousewheel") : (d3_behavior_zoomDelta = function() {
+    return -d3.event.detail;
+  }, "MozMousePixelScroll");
+  function d3_Color() {}
+  d3_Color.prototype.toString = function() {
+    return this.rgb() + "";
+  };
+  d3.hsl = function(h, s, l) {
+    return arguments.length === 1 ? h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : d3_hsl(+h, +s, +l);
+  };
+  function d3_hsl(h, s, l) {
+    return new d3_Hsl(h, s, l);
+  }
+  function d3_Hsl(h, s, l) {
+    this.h = h;
+    this.s = s;
+    this.l = l;
+  }
+  var d3_hslPrototype = d3_Hsl.prototype = new d3_Color();
+  d3_hslPrototype.brighter = function(k) {
+    k = Math.pow(.7, arguments.length ? k : 1);
+    return d3_hsl(this.h, this.s, this.l / k);
+  };
+  d3_hslPrototype.darker = function(k) {
+    k = Math.pow(.7, arguments.length ? k : 1);
+    return d3_hsl(this.h, this.s, k * this.l);
+  };
+  d3_hslPrototype.rgb = function() {
+    return d3_hsl_rgb(this.h, this.s, this.l);
+  };
+  function d3_hsl_rgb(h, s, l) {
+    var m1, m2;
+    h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h;
+    s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s;
+    l = l < 0 ? 0 : l > 1 ? 1 : l;
+    m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
+    m1 = 2 * l - m2;
+    function v(h) {
+      if (h > 360) h -= 360; else if (h < 0) h += 360;
+      if (h < 60) return m1 + (m2 - m1) * h / 60;
+      if (h < 180) return m2;
+      if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
+      return m1;
+    }
+    function vv(h) {
+      return Math.round(v(h) * 255);
+    }
+    return d3_rgb(vv(h + 120), vv(h), vv(h - 120));
+  }
+  var Ï = Math.PI, Îµ = 1e-6, Îµ2 = Îµ * Îµ, d3_radians = Ï / 180, d3_degrees = 180 / Ï;
+  function d3_sgn(x) {
+    return x > 0 ? 1 : x < 0 ? -1 : 0;
+  }
+  function d3_acos(x) {
+    return x > 1 ? 0 : x < -1 ? Ï : Math.acos(x);
+  }
+  function d3_asin(x) {
+    return x > 1 ? Ï / 2 : x < -1 ? -Ï / 2 : Math.asin(x);
+  }
+  function d3_sinh(x) {
+    return (Math.exp(x) - Math.exp(-x)) / 2;
+  }
+  function d3_cosh(x) {
+    return (Math.exp(x) + Math.exp(-x)) / 2;
+  }
+  function d3_haversin(x) {
+    return (x = Math.sin(x / 2)) * x;
+  }
+  d3.hcl = function(h, c, l) {
+    return arguments.length === 1 ? h instanceof d3_Hcl ? d3_hcl(h.h, h.c, h.l) : h instanceof d3_Lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : d3_hcl(+h, +c, +l);
+  };
+  function d3_hcl(h, c, l) {
+    return new d3_Hcl(h, c, l);
+  }
+  function d3_Hcl(h, c, l) {
+    this.h = h;
+    this.c = c;
+    this.l = l;
+  }
+  var d3_hclPrototype = d3_Hcl.prototype = new d3_Color();
+  d3_hclPrototype.brighter = function(k) {
+    return d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)));
+  };
+  d3_hclPrototype.darker = function(k) {
+    return d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)));
+  };
+  d3_hclPrototype.rgb = function() {
+    return d3_hcl_lab(this.h, this.c, this.l).rgb();
+  };
+  function d3_hcl_lab(h, c, l) {
+    if (isNaN(h)) h = 0;
+    if (isNaN(c)) c = 0;
+    return d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c);
+  }
+  d3.lab = function(l, a, b) {
+    return arguments.length === 1 ? l instanceof d3_Lab ? d3_lab(l.l, l.a, l.b) : l instanceof d3_Hcl ? d3_hcl_lab(l.l, l.c, l.h) : d3_rgb_lab((l = d3.rgb(l)).r, l.g, l.b) : d3_lab(+l, +a, +b);
+  };
+  function d3_lab(l, a, b) {
+    return new d3_Lab(l, a, b);
+  }
+  function d3_Lab(l, a, b) {
+    this.l = l;
+    this.a = a;
+    this.b = b;
+  }
+  var d3_lab_K = 18;
+  var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883;
+  var d3_labPrototype = d3_Lab.prototype = new d3_Color();
+  d3_labPrototype.brighter = function(k) {
+    return d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
+  };
+  d3_labPrototype.darker = function(k) {
+    return d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
+  };
+  d3_labPrototype.rgb = function() {
+    return d3_lab_rgb(this.l, this.a, this.b);
+  };
+  function d3_lab_rgb(l, a, b) {
+    var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200;
+    x = d3_lab_xyz(x) * d3_lab_X;
+    y = d3_lab_xyz(y) * d3_lab_Y;
+    z = d3_lab_xyz(z) * d3_lab_Z;
+    return d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z));
+  }
+  function d3_lab_hcl(l, a, b) {
+    return l > 0 ? d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : d3_hcl(NaN, NaN, l);
+  }
+  function d3_lab_xyz(x) {
+    return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
+  }
+  function d3_xyz_lab(x) {
+    return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
+  }
+  function d3_xyz_rgb(r) {
+    return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055));
+  }
+  d3.rgb = function(r, g, b) {
+    return arguments.length === 1 ? r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : d3_rgb(~~r, ~~g, ~~b);
+  };
+  function d3_rgbNumber(value) {
+    return d3_rgb(value >> 16, value >> 8 & 255, value & 255);
+  }
+  function d3_rgbString(value) {
+    return d3_rgbNumber(value) + "";
+  }
+  function d3_rgb(r, g, b) {
+    return new d3_Rgb(r, g, b);
+  }
+  function d3_Rgb(r, g, b) {
+    this.r = r;
+    this.g = g;
+    this.b = b;
+  }
+  var d3_rgbPrototype = d3_Rgb.prototype = new d3_Color();
+  d3_rgbPrototype.brighter = function(k) {
+    k = Math.pow(.7, arguments.length ? k : 1);
+    var r = this.r, g = this.g, b = this.b, i = 30;
+    if (!r && !g && !b) return d3_rgb(i, i, i);
+    if (r && r < i) r = i;
+    if (g && g < i) g = i;
+    if (b && b < i) b = i;
+    return d3_rgb(Math.min(255, ~~(r / k)), Math.min(255, ~~(g / k)), Math.min(255, ~~(b / k)));
+  };
+  d3_rgbPrototype.darker = function(k) {
+    k = Math.pow(.7, arguments.length ? k : 1);
+    return d3_rgb(~~(k * this.r), ~~(k * this.g), ~~(k * this.b));
+  };
+  d3_rgbPrototype.hsl = function() {
+    return d3_rgb_hsl(this.r, this.g, this.b);
+  };
+  d3_rgbPrototype.toString = function() {
+    return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b);
+  };
+  function d3_rgb_hex(v) {
+    return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16);
+  }
+  function d3_rgb_parse(format, rgb, hsl) {
+    var r = 0, g = 0, b = 0, m1, m2, name;
+    m1 = /([a-z]+)\((.*)\)/i.exec(format);
+    if (m1) {
+      m2 = m1[2].split(",");
+      switch (m1[1]) {
+       case "hsl":
+        {
+          return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100);
+        }
+
+       case "rgb":
+        {
+          return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2]));
+        }
+      }
+    }
+    if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b);
+    if (format != null && format.charAt(0) === "#") {
+      if (format.length === 4) {
+        r = format.charAt(1);
+        r += r;
+        g = format.charAt(2);
+        g += g;
+        b = format.charAt(3);
+        b += b;
+      } else if (format.length === 7) {
+        r = format.substring(1, 3);
+        g = format.substring(3, 5);
+        b = format.substring(5, 7);
+      }
+      r = parseInt(r, 16);
+      g = parseInt(g, 16);
+      b = parseInt(b, 16);
+    }
+    return rgb(r, g, b);
+  }
+  function d3_rgb_hsl(r, g, b) {
+    var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2;
+    if (d) {
+      s = l < .5 ? d / (max + min) : d / (2 - max - min);
+      if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4;
+      h *= 60;
+    } else {
+      h = NaN;
+      s = l > 0 && l < 1 ? 0 : h;
+    }
+    return d3_hsl(h, s, l);
+  }
+  function d3_rgb_lab(r, g, b) {
+    r = d3_rgb_xyz(r);
+    g = d3_rgb_xyz(g);
+    b = d3_rgb_xyz(b);
+    var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z);
+    return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
+  }
+  function d3_rgb_xyz(r) {
+    return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4);
+  }
+  function d3_rgb_parseNumber(c) {
+    var f = parseFloat(c);
+    return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f;
+  }
+  var d3_rgb_names = d3.map({
+    aliceblue: 15792383,
+    antiquewhite: 16444375,
+    aqua: 65535,
+    aquamarine: 8388564,
+    azure: 15794175,
+    beige: 16119260,
+    bisque: 16770244,
+    black: 0,
+    blanchedalmond: 16772045,
+    blue: 255,
+    blueviolet: 9055202,
+    brown: 10824234,
+    burlywood: 14596231,
+    cadetblue: 6266528,
+    chartreuse: 8388352,
+    chocolate: 13789470,
+    coral: 16744272,
+    cornflowerblue: 6591981,
+    cornsilk: 16775388,
+    crimson: 14423100,
+    cyan: 65535,
+    darkblue: 139,
+    darkcyan: 35723,
+    darkgoldenrod: 12092939,
+    darkgray: 11119017,
+    darkgreen: 25600,
+    darkgrey: 11119017,
+    darkkhaki: 12433259,
+    darkmagenta: 9109643,
+    darkolivegreen: 5597999,
+    darkorange: 16747520,
+    darkorchid: 10040012,
+    darkred: 9109504,
+    darksalmon: 15308410,
+    darkseagreen: 9419919,
+    darkslateblue: 4734347,
+    darkslategray: 3100495,
+    darkslategrey: 3100495,
+    darkturquoise: 52945,
+    darkviolet: 9699539,
+    deeppink: 16716947,
+    deepskyblue: 49151,
+    dimgray: 6908265,
+    dimgrey: 6908265,
+    dodgerblue: 2003199,
+    firebrick: 11674146,
+    floralwhite: 16775920,
+    forestgreen: 2263842,
+    fuchsia: 16711935,
+    gainsboro: 14474460,
+    ghostwhite: 16316671,
+    gold: 16766720,
+    goldenrod: 14329120,
+    gray: 8421504,
+    green: 32768,
+    greenyellow: 11403055,
+    grey: 8421504,
+    honeydew: 15794160,
+    hotpink: 16738740,
+    indianred: 13458524,
+    indigo: 4915330,
+    ivory: 16777200,
+    khaki: 15787660,
+    lavender: 15132410,
+    lavenderblush: 16773365,
+    lawngreen: 8190976,
+    lemonchiffon: 16775885,
+    lightblue: 11393254,
+    lightcoral: 15761536,
+    lightcyan: 14745599,
+    lightgoldenrodyellow: 16448210,
+    lightgray: 13882323,
+    lightgreen: 9498256,
+    lightgrey: 13882323,
+    lightpink: 16758465,
+    lightsalmon: 16752762,
+    lightseagreen: 2142890,
+    lightskyblue: 8900346,
+    lightslategray: 7833753,
+    lightslategrey: 7833753,
+    lightsteelblue: 11584734,
+    lightyellow: 16777184,
+    lime: 65280,
+    limegreen: 3329330,
+    linen: 16445670,
+    magenta: 16711935,
+    maroon: 8388608,
+    mediumaquamarine: 6737322,
+    mediumblue: 205,
+    mediumorchid: 12211667,
+    mediumpurple: 9662683,
+    mediumseagreen: 3978097,
+    mediumslateblue: 8087790,
+    mediumspringgreen: 64154,
+    mediumturquoise: 4772300,
+    mediumvioletred: 13047173,
+    midnightblue: 1644912,
+    mintcream: 16121850,
+    mistyrose: 16770273,
+    moccasin: 16770229,
+    navajowhite: 16768685,
+    navy: 128,
+    oldlace: 16643558,
+    olive: 8421376,
+    olivedrab: 7048739,
+    orange: 16753920,
+    orangered: 16729344,
+    orchid: 14315734,
+    palegoldenrod: 15657130,
+    palegreen: 10025880,
+    paleturquoise: 11529966,
+    palevioletred: 14381203,
+    papayawhip: 16773077,
+    peachpuff: 16767673,
+    peru: 13468991,
+    pink: 16761035,
+    plum: 14524637,
+    powderblue: 11591910,
+    purple: 8388736,
+    red: 16711680,
+    rosybrown: 12357519,
+    royalblue: 4286945,
+    saddlebrown: 9127187,
+    salmon: 16416882,
+    sandybrown: 16032864,
+    seagreen: 3050327,
+    seashell: 16774638,
+    sienna: 10506797,
+    silver: 12632256,
+    skyblue: 8900331,
+    slateblue: 6970061,
+    slategray: 7372944,
+    slategrey: 7372944,
+    snow: 16775930,
+    springgreen: 65407,
+    steelblue: 4620980,
+    tan: 13808780,
+    teal: 32896,
+    thistle: 14204888,
+    tomato: 16737095,
+    turquoise: 4251856,
+    violet: 15631086,
+    wheat: 16113331,
+    white: 16777215,
+    whitesmoke: 16119285,
+    yellow: 16776960,
+    yellowgreen: 10145074
+  });
+  d3_rgb_names.forEach(function(key, value) {
+    d3_rgb_names.set(key, d3_rgbNumber(value));
+  });
+  function d3_functor(v) {
+    return typeof v === "function" ? v : function() {
+      return v;
+    };
+  }
+  d3.functor = d3_functor;
+  function d3_identity(d) {
+    return d;
+  }
+  d3.xhr = d3_xhrType(d3_identity);
+  function d3_xhrType(response) {
+    return function(url, mimeType, callback) {
+      if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, 
+      mimeType = null;
+      return d3_xhr(url, mimeType, response, callback);
+    };
+  }
+  function d3_xhr(url, mimeType, response, callback) {
+    var xhr = {}, dispatch = d3.dispatch("progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null;
+    if (d3_window.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest();
+    "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() {
+      request.readyState > 3 && respond();
+    };
+    function respond() {
+      var status = request.status, result;
+      if (!status && request.responseText || status >= 200 && status < 300 || status === 304) {
+        try {
+          result = response.call(xhr, request);
+        } catch (e) {
+          dispatch.error.call(xhr, e);
+          return;
+        }
+        dispatch.load.call(xhr, result);
+      } else {
+        dispatch.error.call(xhr, request);
+      }
+    }
+    request.onprogress = function(event) {
+      var o = d3.event;
+      d3.event = event;
+      try {
+        dispatch.progress.call(xhr, request);
+      } finally {
+        d3.event = o;
+      }
+    };
+    xhr.header = function(name, value) {
+      name = (name + "").toLowerCase();
+      if (arguments.length < 2) return headers[name];
+      if (value == null) delete headers[name]; else headers[name] = value + "";
+      return xhr;
+    };
+    xhr.mimeType = function(value) {
+      if (!arguments.length) return mimeType;
+      mimeType = value == null ? null : value + "";
+      return xhr;
+    };
+    xhr.responseType = function(value) {
+      if (!arguments.length) return responseType;
+      responseType = value;
+      return xhr;
+    };
+    xhr.response = function(value) {
+      response = value;
+      return xhr;
+    };
+    [ "get", "post" ].forEach(function(method) {
+      xhr[method] = function() {
+        return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments)));
+      };
+    });
+    xhr.send = function(method, data, callback) {
+      if (arguments.length === 2 && typeof data === "function") callback = data, data = null;
+      request.open(method, url, true);
+      if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*";
+      if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]);
+      if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType);
+      if (responseType != null) request.responseType = responseType;
+      if (callback != null) xhr.on("error", callback).on("load", function(request) {
+        callback(null, request);
+      });
+      request.send(data == null ? null : data);
+      return xhr;
+    };
+    xhr.abort = function() {
+      request.abort();
+      return xhr;
+    };
+    d3.rebind(xhr, dispatch, "on");
+    return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback));
+  }
+  function d3_xhr_fixCallback(callback) {
+    return callback.length === 1 ? function(error, request) {
+      callback(error == null ? request : null);
+    } : callback;
+  }
+  d3.dsv = function(delimiter, mimeType) {
+    var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0);
+    function dsv(url, row, callback) {
+      if (arguments.length < 3) callback = row, row = null;
+      var xhr = d3.xhr(url, mimeType, callback);
+      xhr.row = function(_) {
+        return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row;
+      };
+      return xhr.row(row);
+    }
+    function response(request) {
+      return dsv.parse(request.responseText);
+    }
+    function typedResponse(f) {
+      return function(request) {
+        return dsv.parse(request.responseText, f);
+      };
+    }
+    dsv.parse = function(text, f) {
+      var o;
+      return dsv.parseRows(text, function(row, i) {
+        if (o) return o(row, i - 1);
+        var a = new Function("d", "return {" + row.map(function(name, i) {
+          return JSON.stringify(name) + ": d[" + i + "]";
+        }).join(",") + "}");
+        o = f ? function(row, i) {
+          return f(a(row), i);
+        } : a;
+      });
+    };
+    dsv.parseRows = function(text, f) {
+      var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol;
+      function token() {
+        if (I >= N) return EOF;
+        if (eol) return eol = false, EOL;
+        var j = I;
+        if (text.charCodeAt(j) === 34) {
+          var i = j;
+          while (i++ < N) {
+            if (text.charCodeAt(i) === 34) {
+              if (text.charCodeAt(i + 1) !== 34) break;
+              ++i;
+            }
+          }
+          I = i + 2;
+          var c = text.charCodeAt(i + 1);
+          if (c === 13) {
+            eol = true;
+            if (text.charCodeAt(i + 2) === 10) ++I;
+          } else if (c === 10) {
+            eol = true;
+          }
+          return text.substring(j + 1, i).replace(/""/g, '"');
+        }
+        while (I < N) {
+          var c = text.charCodeAt(I++), k = 1;
+          if (c === 10) eol = true; else if (c === 13) {
+            eol = true;
+            if (text.charCodeAt(I) === 10) ++I, ++k;
+          } else if (c !== delimiterCode) continue;
+          return text.substring(j, I - k);
+        }
+        return text.substring(j);
+      }
+      while ((t = token()) !== EOF) {
+        var a = [];
+        while (t !== EOL && t !== EOF) {
+          a.push(t);
+          t = token();
+        }
+        if (f && !(a = f(a, n++))) continue;
+        rows.push(a);
+      }
+      return rows;
+    };
+    dsv.format = function(rows) {
+      if (Array.isArray(rows[0])) return dsv.formatRows(rows);
+      var fieldSet = new d3_Set(), fields = [];
+      rows.forEach(function(row) {
+        for (var field in row) {
+          if (!fieldSet.has(field)) {
+            fields.push(fieldSet.add(field));
+          }
+        }
+      });
+      return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) {
+        return fields.map(function(field) {
+          return formatValue(row[field]);
+        }).join(delimiter);
+      })).join("\n");
+    };
+    dsv.formatRows = function(rows) {
+      return rows.map(formatRow).join("\n");
+    };
+    function formatRow(row) {
+      return row.map(formatValue).join(delimiter);
+    }
+    function formatValue(text) {
+      return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text;
+    }
+    return dsv;
+  };
+  d3.csv = d3.dsv(",", "text/csv");
+  d3.tsv = d3.dsv("	", "text/tab-separated-values");
+  var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_active, d3_timer_frame = d3_window[d3_vendorSymbol(d3_window, "requestAnimationFrame")] || function(callback) {
+    setTimeout(callback, 17);
+  };
+  d3.timer = function(callback, delay, then) {
+    var n = arguments.length;
+    if (n < 2) delay = 0;
+    if (n < 3) then = Date.now();
+    var time = then + delay, timer = {
+      callback: callback,
+      time: time,
+      next: null
+    };
+    if (d3_timer_queueTail) d3_timer_queueTail.next = timer; else d3_timer_queueHead = timer;
+    d3_timer_queueTail = timer;
+    if (!d3_timer_interval) {
+      d3_timer_timeout = clearTimeout(d3_timer_timeout);
+      d3_timer_interval = 1;
+      d3_timer_frame(d3_timer_step);
+    }
+  };
+  function d3_timer_step() {
+    var now = d3_timer_mark(), delay = d3_timer_sweep() - now;
+    if (delay > 24) {
+      if (isFinite(delay)) {
+        clearTimeout(d3_timer_timeout);
+        d3_timer_timeout = setTimeout(d3_timer_step, delay);
+      }
+      d3_timer_interval = 0;
+    } else {
+      d3_timer_interval = 1;
+      d3_timer_frame(d3_timer_step);
+    }
+  }
+  d3.timer.flush = function() {
+    d3_timer_mark();
+    d3_timer_sweep();
+  };
+  function d3_timer_replace(callback, delay, then) {
+    var n = arguments.length;
+    if (n < 2) delay = 0;
+    if (n < 3) then = Date.now();
+    d3_timer_active.callback = callback;
+    d3_timer_active.time = then + delay;
+  }
+  function d3_timer_mark() {
+    var now = Date.now();
+    d3_timer_active = d3_timer_queueHead;
+    while (d3_timer_active) {
+      if (now >= d3_timer_active.time) d3_timer_active.flush = d3_timer_active.callback(now - d3_timer_active.time);
+      d3_timer_active = d3_timer_active.next;
+    }
+    return now;
+  }
+  function d3_timer_sweep() {
+    var t0, t1 = d3_timer_queueHead, time = Infinity;
+    while (t1) {
+      if (t1.flush) {
+        t1 = t0 ? t0.next = t1.next : d3_timer_queueHead = t1.next;
+      } else {
+        if (t1.time < time) time = t1.time;
+        t1 = (t0 = t1).next;
+      }
+    }
+    d3_timer_queueTail = t0;
+    return time;
+  }
+  var d3_format_decimalPoint = ".", d3_format_thousandsSeparator = ",", d3_format_grouping = [ 3, 3 ], d3_format_currencySymbol = "$";
+  var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "Âµ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix);
+  d3.formatPrefix = function(value, precision) {
+    var i = 0;
+    if (value) {
+      if (value < 0) value *= -1;
+      if (precision) value = d3.round(value, d3_format_precision(value, precision));
+      i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
+      i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3));
+    }
+    return d3_formatPrefixes[8 + i / 3];
+  };
+  function d3_formatPrefix(d, i) {
+    var k = Math.pow(10, Math.abs(8 - i) * 3);
+    return {
+      scale: i > 8 ? function(d) {
+        return d / k;
+      } : function(d) {
+        return d * k;
+      },
+      symbol: d
+    };
+  }
+  d3.round = function(x, n) {
+    return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x);
+  };
+  d3.format = function(specifier) {
+    var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, suffix = "", integer = false;
+    if (precision) precision = +precision.substring(1);
+    if (zfill || fill === "0" && align === "=") {
+      zfill = fill = "0";
+      align = "=";
+      if (comma) width -= Math.floor((width - 1) / 4);
+    }
+    switch (type) {
+     case "n":
+      comma = true;
+      type = "g";
+      break;
+
+     case "%":
+      scale = 100;
+      suffix = "%";
+      type = "f";
+      break;
+
+     case "p":
+      scale = 100;
+      suffix = "%";
+      type = "r";
+      break;
+
+     case "b":
+     case "o":
+     case "x":
+     case "X":
+      if (symbol === "#") symbol = "0" + type.toLowerCase();
+
+     case "c":
+     case "d":
+      integer = true;
+      precision = 0;
+      break;
+
+     case "s":
+      scale = -1;
+      type = "r";
+      break;
+    }
+    if (symbol === "#") symbol = ""; else if (symbol === "$") symbol = d3_format_currencySymbol;
+    if (type == "r" && !precision) type = "g";
+    if (precision != null) {
+      if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision));
+    }
+    type = d3_format_types.get(type) || d3_format_typeDefault;
+    var zcomma = zfill && comma;
+    return function(value) {
+      if (integer && value % 1) return "";
+      var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign;
+      if (scale < 0) {
+        var prefix = d3.formatPrefix(value, precision);
+        value = prefix.scale(value);
+        suffix = prefix.symbol;
+      } else {
+        value *= scale;
+      }
+      value = type(value, precision);
+      var i = value.lastIndexOf("."), before = i < 0 ? value : value.substring(0, i), after = i < 0 ? "" : d3_format_decimalPoint + value.substring(i + 1);
+      if (!zfill && comma) before = d3_format_group(before);
+      var length = symbol.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : "";
+      if (zcomma) before = d3_format_group(padding + before);
+      negative += symbol;
+      value = before + after;
+      return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + suffix;
+    };
+  };
+  var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
+  var d3_format_types = d3.map({
+    b: function(x) {
+      return x.toString(2);
+    },
+    c: function(x) {
+      return String.fromCharCode(x);
+    },
+    o: function(x) {
+      return x.toString(8);
+    },
+    x: function(x) {
+      return x.toString(16);
+    },
+    X: function(x) {
+      return x.toString(16).toUpperCase();
+    },
+    g: function(x, p) {
+      return x.toPrecision(p);
+    },
+    e: function(x, p) {
+      return x.toExponential(p);
+    },
+    f: function(x, p) {
+      return x.toFixed(p);
+    },
+    r: function(x, p) {
+      return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p))));
+    }
+  });
+  function d3_format_precision(x, p) {
+    return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1);
+  }
+  function d3_format_typeDefault(x) {
+    return x + "";
+  }
+  var d3_format_group = d3_identity;
+  if (d3_format_grouping) {
+    var d3_format_groupingLength = d3_format_grouping.length;
+    d3_format_group = function(value) {
+      var i = value.length, t = [], j = 0, g = d3_format_grouping[0];
+      while (i > 0 && g > 0) {
+        t.push(value.substring(i -= g, i + g));
+        g = d3_format_grouping[j = (j + 1) % d3_format_groupingLength];
+      }
+      return t.reverse().join(d3_format_thousandsSeparator);
+    };
+  }
+  d3.geo = {};
+  function d3_adder() {}
+  d3_adder.prototype = {
+    s: 0,
+    t: 0,
+    add: function(y) {
+      d3_adderSum(y, this.t, d3_adderTemp);
+      d3_adderSum(d3_adderTemp.s, this.s, this);
+      if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t;
+    },
+    reset: function() {
+      this.s = this.t = 0;
+    },
+    valueOf: function() {
+      return this.s;
+    }
+  };
+  var d3_adderTemp = new d3_adder();
+  function d3_adderSum(a, b, o) {
+    var x = o.s = a + b, bv = x - a, av = x - bv;
+    o.t = a - av + (b - bv);
+  }
+  d3.geo.stream = function(object, listener) {
+    if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) {
+      d3_geo_streamObjectType[object.type](object, listener);
+    } else {
+      d3_geo_streamGeometry(object, listener);
+    }
+  };
+  function d3_geo_streamGeometry(geometry, listener) {
+    if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) {
+      d3_geo_streamGeometryType[geometry.type](geometry, listener);
+    }
+  }
+  var d3_geo_streamObjectType = {
+    Feature: function(feature, listener) {
+      d3_geo_streamGeometry(feature.geometry, listener);
+    },
+    FeatureCollection: function(object, listener) {
+      var features = object.features, i = -1, n = features.length;
+      while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener);
+    }
+  };
+  var d3_geo_streamGeometryType = {
+    Sphere: function(object, listener) {
+      listener.sphere();
+    },
+    Point: function(object, listener) {
+      var coordinate = object.coordinates;
+      listener.point(coordinate[0], coordinate[1]);
+    },
+    MultiPoint: function(object, listener) {
+      var coordinates = object.coordinates, i = -1, n = coordinates.length, coordinate;
+      while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]);
+    },
+    LineString: function(object, listener) {
+      d3_geo_streamLine(object.coordinates, listener, 0);
+    },
+    MultiLineString: function(object, listener) {
+      var coordinates = object.coordinates, i = -1, n = coordinates.length;
+      while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0);
+    },
+    Polygon: function(object, listener) {
+      d3_geo_streamPolygon(object.coordinates, listener);
+    },
+    MultiPolygon: function(object, listener) {
+      var coordinates = object.coordinates, i = -1, n = coordinates.length;
+      while (++i < n) d3_geo_streamPolygon(coordinates[i], listener);
+    },
+    GeometryCollection: function(object, listener) {
+      var geometries = object.geometries, i = -1, n = geometries.length;
+      while (++i < n) d3_geo_streamGeometry(geometries[i], listener);
+    }
+  };
+  function d3_geo_streamLine(coordinates, listener, closed) {
+    var i = -1, n = coordinates.length - closed, coordinate;
+    listener.lineStart();
+    while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]);
+    listener.lineEnd();
+  }
+  function d3_geo_streamPolygon(coordinates, listener) {
+    var i = -1, n = coordinates.length;
+    listener.polygonStart();
+    while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1);
+    listener.polygonEnd();
+  }
+  d3.geo.area = function(object) {
+    d3_geo_areaSum = 0;
+    d3.geo.stream(object, d3_geo_area);
+    return d3_geo_areaSum;
+  };
+  var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder();
+  var d3_geo_area = {
+    sphere: function() {
+      d3_geo_areaSum += 4 * Ï;
+    },
+    point: d3_noop,
+    lineStart: d3_noop,
+    lineEnd: d3_noop,
+    polygonStart: function() {
+      d3_geo_areaRingSum.reset();
+      d3_geo_area.lineStart = d3_geo_areaRingStart;
+    },
+    polygonEnd: function() {
+      var area = 2 * d3_geo_areaRingSum;
+      d3_geo_areaSum += area < 0 ? 4 * Ï + area : area;
+      d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop;
+    }
+  };
+  function d3_geo_areaRingStart() {
+    var Î»00, Ï00, Î»0, cosÏ0, sinÏ0;
+    d3_geo_area.point = function(Î», Ï) {
+      d3_geo_area.point = nextPoint;
+      Î»0 = (Î»00 = Î») * d3_radians, cosÏ0 = Math.cos(Ï = (Ï00 = Ï) * d3_radians / 2 + Ï / 4), 
+      sinÏ0 = Math.sin(Ï);
+    };
+    function nextPoint(Î», Ï) {
+      Î» *= d3_radians;
+      Ï = Ï * d3_radians / 2 + Ï / 4;
+      var dÎ» = Î» - Î»0, cosÏ = Math.cos(Ï), sinÏ = Math.sin(Ï), k = sinÏ0 * sinÏ, u = cosÏ0 * cosÏ + k * Math.cos(dÎ»), v = k * Math.sin(dÎ»);
+      d3_geo_areaRingSum.add(Math.atan2(v, u));
+      Î»0 = Î», cosÏ0 = cosÏ, sinÏ0 = sinÏ;
+    }
+    d3_geo_area.lineEnd = function() {
+      nextPoint(Î»00, Ï00);
+    };
+  }
+  function d3_geo_cartesian(spherical) {
+    var Î» = spherical[0], Ï = spherical[1], cosÏ = Math.cos(Ï);
+    return [ cosÏ * Math.cos(Î»), cosÏ * Math.sin(Î»), Math.sin(Ï) ];
+  }
+  function d3_geo_cartesianDot(a, b) {
+    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+  }
+  function d3_geo_cartesianCross(a, b) {
+    return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ];
+  }
+  function d3_geo_cartesianAdd(a, b) {
+    a[0] += b[0];
+    a[1] += b[1];
+    a[2] += b[2];
+  }
+  function d3_geo_cartesianScale(vector, k) {
+    return [ vector[0] * k, vector[1] * k, vector[2] * k ];
+  }
+  function d3_geo_cartesianNormalize(d) {
+    var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
+    d[0] /= l;
+    d[1] /= l;
+    d[2] /= l;
+  }
+  function d3_geo_spherical(cartesian) {
+    return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ];
+  }
+  function d3_geo_sphericalEqual(a, b) {
+    return Math.abs(a[0] - b[0]) < Îµ && Math.abs(a[1] - b[1]) < Îµ;
+  }
+  d3.geo.bounds = function() {
+    var Î»0, Ï0, Î»1, Ï1, Î»_, Î»__, Ï__, p0, dÎ»Sum, ranges, range;
+    var bound = {
+      point: point,
+      lineStart: lineStart,
+      lineEnd: lineEnd,
+      polygonStart: function() {
+        bound.point = ringPoint;
+        bound.lineStart = ringStart;
+        bound.lineEnd = ringEnd;
+        dÎ»Sum = 0;
+        d3_geo_area.polygonStart();
+      },
+      polygonEnd: function() {
+        d3_geo_area.polygonEnd();
+        bound.point = point;
+        bound.lineStart = lineStart;
+        bound.lineEnd = lineEnd;
+        if (d3_geo_areaRingSum < 0) Î»0 = -(Î»1 = 180), Ï0 = -(Ï1 = 90); else if (dÎ»Sum > Îµ) Ï1 = 90; else if (dÎ»Sum < -Îµ) Ï0 = -90;
+        range[0] = Î»0, range[1] = Î»1;
+      }
+    };
+    function point(Î», Ï) {
+      ranges.push(range = [ Î»0 = Î», Î»1 = Î» ]);
+      if (Ï < Ï0) Ï0 = Ï;
+      if (Ï > Ï1) Ï1 = Ï;
+    }
+    function linePoint(Î», Ï) {
+      var p = d3_geo_cartesian([ Î» * d3_radians, Ï * d3_radians ]);
+      if (p0) {
+        var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal);
+        d3_geo_cartesianNormalize(inflection);
+        inflection = d3_geo_spherical(inflection);
+        var dÎ» = Î» - Î»_, s = dÎ» > 0 ? 1 : -1, Î»i = inflection[0] * d3_degrees * s, antimeridian = Math.abs(dÎ») > 180;
+        if (antimeridian ^ (s * Î»_ < Î»i && Î»i < s * Î»)) {
+          var Ïi = inflection[1] * d3_degrees;
+          if (Ïi > Ï1) Ï1 = Ïi;
+        } else if (Î»i = (Î»i + 360) % 360 - 180, antimeridian ^ (s * Î»_ < Î»i && Î»i < s * Î»)) {
+          var Ïi = -inflection[1] * d3_degrees;
+          if (Ïi < Ï0) Ï0 = Ïi;
+        } else {
+          if (Ï < Ï0) Ï0 = Ï;
+          if (Ï > Ï1) Ï1 = Ï;
+        }
+        if (antimeridian) {
+          if (Î» < Î»_) {
+            if (angle(Î»0, Î») > angle(Î»0, Î»1)) Î»1 = Î»;
+          } else {
+            if (angle(Î», Î»1) > angle(Î»0, Î»1)) Î»0 = Î»;
+          }
+        } else {
+          if (Î»1 >= Î»0) {
+            if (Î» < Î»0) Î»0 = Î»;
+            if (Î» > Î»1) Î»1 = Î»;
+          } else {
+            if (Î» > Î»_) {
+              if (angle(Î»0, Î») > angle(Î»0, Î»1)) Î»1 = Î»;
+            } else {
+              if (angle(Î», Î»1) > angle(Î»0, Î»1)) Î»0 = Î»;
+            }
+          }
+        }
+      } else {
+        point(Î», Ï);
+      }
+      p0 = p, Î»_ = Î»;
+    }
+    function lineStart() {
+      bound.point = linePoint;
+    }
+    function lineEnd() {
+      range[0] = Î»0, range[1] = Î»1;
+      bound.point = point;
+      p0 = null;
+    }
+    function ringPoint(Î», Ï) {
+      if (p0) {
+        var dÎ» = Î» - Î»_;
+        dÎ»Sum += Math.abs(dÎ») > 180 ? dÎ» + (dÎ» > 0 ? 360 : -360) : dÎ»;
+      } else Î»__ = Î», Ï__ = Ï;
+      d3_geo_area.point(Î», Ï);
+      linePoint(Î», Ï);
+    }
+    function ringStart() {
+      d3_geo_area.lineStart();
+    }
+    function ringEnd() {
+      ringPoint(Î»__, Ï__);
+      d3_geo_area.lineEnd();
+      if (Math.abs(dÎ»Sum) > Îµ) Î»0 = -(Î»1 = 180);
+      range[0] = Î»0, range[1] = Î»1;
+      p0 = null;
+    }
+    function angle(Î»0, Î»1) {
+      return (Î»1 -= Î»0) < 0 ? Î»1 + 360 : Î»1;
+    }
+    function compareRanges(a, b) {
+      return a[0] - b[0];
+    }
+    function withinRange(x, range) {
+      return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x;
+    }
+    return function(feature) {
+      Ï1 = Î»1 = -(Î»0 = Ï0 = Infinity);
+      ranges = [];
+      d3.geo.stream(feature, bound);
+      var n = ranges.length;
+      if (n) {
+        ranges.sort(compareRanges);
+        for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) {
+          b = ranges[i];
+          if (withinRange(b[0], a) || withinRange(b[1], a)) {
+            if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
+            if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
+          } else {
+            merged.push(a = b);
+          }
+        }
+        var best = -Infinity, dÎ»;
+        for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) {
+          b = merged[i];
+          if ((dÎ» = angle(a[1], b[0])) > best) best = dÎ», Î»0 = b[0], Î»1 = a[1];
+        }
+      }
+      ranges = range = null;
+      return Î»0 === Infinity || Ï0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ Î»0, Ï0 ], [ Î»1, Ï1 ] ];
+    };
+  }();
+  d3.geo.centroid = function(object) {
+    d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
+    d3.geo.stream(object, d3_geo_centroid);
+    var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z;
+    if (m < Îµ2) {
+      x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1;
+      if (d3_geo_centroidW1 < Îµ) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0;
+      m = x * x + y * y + z * z;
+      if (m < Îµ2) return [ NaN, NaN ];
+    }
+    return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ];
+  };
+  var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2;
+  var d3_geo_centroid = {
+    sphere: d3_noop,
+    point: d3_geo_centroidPoint,
+    lineStart: d3_geo_centroidLineStart,
+    lineEnd: d3_geo_centroidLineEnd,
+    polygonStart: function() {
+      d3_geo_centroid.lineStart = d3_geo_centroidRingStart;
+    },
+    polygonEnd: function() {
+      d3_geo_centroid.lineStart = d3_geo_centroidLineStart;
+    }
+  };
+  function d3_geo_centroidPoint(Î», Ï) {
+    Î» *= d3_radians;
+    var cosÏ = Math.cos(Ï *= d3_radians);
+    d3_geo_centroidPointXYZ(cosÏ * Math.cos(Î»), cosÏ * Math.sin(Î»), Math.sin(Ï));
+  }
+  function d3_geo_centroidPointXYZ(x, y, z) {
+    ++d3_geo_centroidW0;
+    d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0;
+    d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0;
+    d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0;
+  }
+  function d3_geo_centroidLineStart() {
+    var x0, y0, z0;
+    d3_geo_centroid.point = function(Î», Ï) {
+      Î» *= d3_radians;
+      var cosÏ = Math.cos(Ï *= d3_radians);
+      x0 = cosÏ * Math.cos(Î»);
+      y0 = cosÏ * Math.sin(Î»);
+      z0 = Math.sin(Ï);
+      d3_geo_centroid.point = nextPoint;
+      d3_geo_centroidPointXYZ(x0, y0, z0);
+    };
+    function nextPoint(Î», Ï) {
+      Î» *= d3_radians;
+      var cosÏ = Math.cos(Ï *= d3_radians), x = cosÏ * Math.cos(Î»), y = cosÏ * Math.sin(Î»), z = Math.sin(Ï), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z);
+      d3_geo_centroidW1 += w;
+      d3_geo_centroidX1 += w * (x0 + (x0 = x));
+      d3_geo_centroidY1 += w * (y0 + (y0 = y));
+      d3_geo_centroidZ1 += w * (z0 + (z0 = z));
+      d3_geo_centroidPointXYZ(x0, y0, z0);
+    }
+  }
+  function d3_geo_centroidLineEnd() {
+    d3_geo_centroid.point = d3_geo_centroidPoint;
+  }
+  function d3_geo_centroidRingStart() {
+    var Î»00, Ï00, x0, y0, z0;
+    d3_geo_centroid.point = function(Î», Ï) {
+      Î»00 = Î», Ï00 = Ï;
+      d3_geo_centroid.point = nextPoint;
+      Î» *= d3_radians;
+      var cosÏ = Math.cos(Ï *= d3_radians);
+      x0 = cosÏ * Math.cos(Î»);
+      y0 = cosÏ * Math.sin(Î»);
+      z0 = Math.sin(Ï);
+      d3_geo_centroidPointXYZ(x0, y0, z0);
+    };
+    d3_geo_centroid.lineEnd = function() {
+      nextPoint(Î»00, Ï00);
+      d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
+      d3_geo_centroid.point = d3_geo_centroidPoint;
+    };
+    function nextPoint(Î», Ï) {
+      Î» *= d3_radians;
+      var cosÏ = Math.cos(Ï *= d3_radians), x = cosÏ * Math.cos(Î»), y = cosÏ * Math.sin(Î»), z = Math.sin(Ï), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u);
+      d3_geo_centroidX2 += v * cx;
+      d3_geo_centroidY2 += v * cy;
+      d3_geo_centroidZ2 += v * cz;
+      d3_geo_centroidW1 += w;
+      d3_geo_centroidX1 += w * (x0 + (x0 = x));
+      d3_geo_centroidY1 += w * (y0 + (y0 = y));
+      d3_geo_centroidZ1 += w * (z0 + (z0 = z));
+      d3_geo_centroidPointXYZ(x0, y0, z0);
+    }
+  }
+  function d3_true() {
+    return true;
+  }
+  function d3_geo_clipPolygon(segments, compare, inside, interpolate, listener) {
+    var subject = [], clip = [];
+    segments.forEach(function(segment) {
+      if ((n = segment.length - 1) <= 0) return;
+      var n, p0 = segment[0], p1 = segment[n];
+      if (d3_geo_sphericalEqual(p0, p1)) {
+        listener.lineStart();
+        for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]);
+        listener.lineEnd();
+        return;
+      }
+      var a = {
+        point: p0,
+        points: segment,
+        other: null,
+        visited: false,
+        entry: true,
+        subject: true
+      }, b = {
+        point: p0,
+        points: [ p0 ],
+        other: a,
+        visited: false,
+        entry: false,
+        subject: false
+      };
+      a.other = b;
+      subject.push(a);
+      clip.push(b);
+      a = {
+        point: p1,
+        points: [ p1 ],
+        other: null,
+        visited: false,
+        entry: false,
+        subject: true
+      };
+      b = {
+        point: p1,
+        points: [ p1 ],
+        other: a,
+        visited: false,
+        entry: true,
+        subject: false
+      };
+      a.other = b;
+      subject.push(a);
+      clip.push(b);
+    });
+    clip.sort(compare);
+    d3_geo_clipPolygonLinkCircular(subject);
+    d3_geo_clipPolygonLinkCircular(clip);
+    if (!subject.length) return;
+    if (inside) for (var i = 1, e = !inside(clip[0].point), n = clip.length; i < n; ++i) {
+      clip[i].entry = e = !e;
+    }
+    var start = subject[0], current, points, point;
+    while (1) {
+      current = start;
+      while (current.visited) if ((current = current.next) === start) return;
+      points = current.points;
+      listener.lineStart();
+      do {
+        current.visited = current.other.visited = true;
+        if (current.entry) {
+          if (current.subject) {
+            for (var i = 0; i < points.length; i++) listener.point((point = points[i])[0], point[1]);
+          } else {
+            interpolate(current.point, current.next.point, 1, listener);
+          }
+          current = current.next;
+        } else {
+          if (current.subject) {
+            points = current.prev.points;
+            for (var i = points.length; --i >= 0; ) listener.point((point = points[i])[0], point[1]);
+          } else {
+            interpolate(current.point, current.prev.point, -1, listener);
+          }
+          current = current.prev;
+        }
+        current = current.other;
+        points = current.points;
+      } while (!current.visited);
+      listener.lineEnd();
+    }
+  }
+  function d3_geo_clipPolygonLinkCircular(array) {
+    if (!(n = array.length)) return;
+    var n, i = 0, a = array[0], b;
+    while (++i < n) {
+      a.next = b = array[i];
+      b.prev = a;
+      a = b;
+    }
+    a.next = b = array[0];
+    b.prev = a;
+  }
+  function d3_geo_clip(pointVisible, clipLine, interpolate, polygonContains) {
+    return function(listener) {
+      var line = clipLine(listener);
+      var clip = {
+        point: point,
+        lineStart: lineStart,
+        lineEnd: lineEnd,
+        polygonStart: function() {
+          clip.point = pointRing;
+          clip.lineStart = ringStart;
+          clip.lineEnd = ringEnd;
+          segments = [];
+          polygon = [];
+          listener.polygonStart();
+        },
+        polygonEnd: function() {
+          clip.point = point;
+          clip.lineStart = lineStart;
+          clip.lineEnd = lineEnd;
+          segments = d3.merge(segments);
+          if (segments.length) {
+            d3_geo_clipPolygon(segments, d3_geo_clipSort, null, interpolate, listener);
+          } else if (polygonContains(polygon)) {
+            listener.lineStart();
+            interpolate(null, null, 1, listener);
+            listener.lineEnd();
+          }
+          listener.polygonEnd();
+          segments = polygon = null;
+        },
+        sphere: function() {
+          listener.polygonStart();
+          listener.lineStart();
+          interpolate(null, null, 1, listener);
+          listener.lineEnd();
+          listener.polygonEnd();
+        }
+      };
+      function point(Î», Ï) {
+        if (pointVisible(Î», Ï)) listener.point(Î», Ï);
+      }
+      function pointLine(Î», Ï) {
+        line.point(Î», Ï);
+      }
+      function lineStart() {
+        clip.point = pointLine;
+        line.lineStart();
+      }
+      function lineEnd() {
+        clip.point = point;
+        line.lineEnd();
+      }
+      var segments;
+      var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygon, ring;
+      function pointRing(Î», Ï) {
+        ringListener.point(Î», Ï);
+        ring.push([ Î», Ï ]);
+      }
+      function ringStart() {
+        ringListener.lineStart();
+        ring = [];
+      }
+      function ringEnd() {
+        pointRing(ring[0][0], ring[0][1]);
+        ringListener.lineEnd();
+        var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length;
+        ring.pop();
+        polygon.push(ring);
+        ring = null;
+        if (!n) return;
+        if (clean & 1) {
+          segment = ringSegments[0];
+          var n = segment.length - 1, i = -1, point;
+          listener.lineStart();
+          while (++i < n) listener.point((point = segment[i])[0], point[1]);
+          listener.lineEnd();
+          return;
+        }
+        if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
+        segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
+      }
+      return clip;
+    };
+  }
+  function d3_geo_clipSegmentLength1(segment) {
+    return segment.length > 1;
+  }
+  function d3_geo_clipBufferListener() {
+    var lines = [], line;
+    return {
+      lineStart: function() {
+        lines.push(line = []);
+      },
+      point: function(Î», Ï) {
+        line.push([ Î», Ï ]);
+      },
+      lineEnd: d3_noop,
+      buffer: function() {
+        var buffer = lines;
+        lines = [];
+        line = null;
+        return buffer;
+      },
+      rejoin: function() {
+        if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
+      }
+    };
+  }
+  function d3_geo_clipSort(a, b) {
+    return ((a = a.point)[0] < 0 ? a[1] - Ï / 2 - Îµ : Ï / 2 - a[1]) - ((b = b.point)[0] < 0 ? b[1] - Ï / 2 - Îµ : Ï / 2 - b[1]);
+  }
+  function d3_geo_pointInPolygon(point, polygon) {
+    var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, polar = false, southPole = false, winding = 0;
+    d3_geo_areaRingSum.reset();
+    for (var i = 0, n = polygon.length; i < n; ++i) {
+      var ring = polygon[i], m = ring.length;
+      if (!m) continue;
+      var point0 = ring[0], Î»0 = point0[0], Ï0 = point0[1] / 2 + Ï / 4, sinÏ0 = Math.sin(Ï0), cosÏ0 = Math.cos(Ï0), j = 1;
+      while (true) {
+        if (j === m) j = 0;
+        point = ring[j];
+        var Î» = point[0], Ï = point[1] / 2 + Ï / 4, sinÏ = Math.sin(Ï), cosÏ = Math.cos(Ï), dÎ» = Î» - Î»0, antimeridian = Math.abs(dÎ») > Ï, k = sinÏ0 * sinÏ;
+        d3_geo_areaRingSum.add(Math.atan2(k * Math.sin(dÎ»), cosÏ0 * cosÏ + k * Math.cos(dÎ»)));
+        if (Math.abs(Ï) < Îµ) southPole = true;
+        polarAngle += antimeridian ? dÎ» + (dÎ» >= 0 ? 2 : -2) * Ï : dÎ»;
+        if (antimeridian ^ Î»0 >= meridian ^ Î» >= meridian) {
+          var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point));
+          d3_geo_cartesianNormalize(arc);
+          var intersection = d3_geo_cartesianCross(meridianNormal, arc);
+          d3_geo_cartesianNormalize(intersection);
+          var Ïarc = (antimeridian ^ dÎ» >= 0 ? -1 : 1) * d3_asin(intersection[2]);
+          if (parallel > Ïarc) {
+            winding += antimeridian ^ dÎ» >= 0 ? 1 : -1;
+          }
+        }
+        if (!j++) break;
+        Î»0 = Î», sinÏ0 = sinÏ, cosÏ0 = cosÏ, point0 = point;
+      }
+      if (Math.abs(polarAngle) > Îµ) polar = true;
+    }
+    return (!southPole && !polar && d3_geo_areaRingSum < 0 || polarAngle < -Îµ) ^ winding & 1;
+  }
+  var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, d3_geo_clipAntimeridianPolygonContains);
+  function d3_geo_clipAntimeridianLine(listener) {
+    var Î»0 = NaN, Ï0 = NaN, sÎ»0 = NaN, clean;
+    return {
+      lineStart: function() {
+        listener.lineStart();
+        clean = 1;
+      },
+      point: function(Î»1, Ï1) {
+        var sÎ»1 = Î»1 > 0 ? Ï : -Ï, dÎ» = Math.abs(Î»1 - Î»0);
+        if (Math.abs(dÎ» - Ï) < Îµ) {
+          listener.point(Î»0, Ï0 = (Ï0 + Ï1) / 2 > 0 ? Ï / 2 : -Ï / 2);
+          listener.point(sÎ»0, Ï0);
+          listener.lineEnd();
+          listener.lineStart();
+          listener.point(sÎ»1, Ï0);
+          listener.point(Î»1, Ï0);
+          clean = 0;
+        } else if (sÎ»0 !== sÎ»1 && dÎ» >= Ï) {
+          if (Math.abs(Î»0 - sÎ»0) < Îµ) Î»0 -= sÎ»0 * Îµ;
+          if (Math.abs(Î»1 - sÎ»1) < Îµ) Î»1 -= sÎ»1 * Îµ;
+          Ï0 = d3_geo_clipAntimeridianIntersect(Î»0, Ï0, Î»1, Ï1);
+          listener.point(sÎ»0, Ï0);
+          listener.lineEnd();
+          listener.lineStart();
+          listener.point(sÎ»1, Ï0);
+          clean = 0;
+        }
+        listener.point(Î»0 = Î»1, Ï0 = Ï1);
+        sÎ»0 = sÎ»1;
+      },
+      lineEnd: function() {
+        listener.lineEnd();
+        Î»0 = Ï0 = NaN;
+      },
+      clean: function() {
+        return 2 - clean;
+      }
+    };
+  }
+  function d3_geo_clipAntimeridianIntersect(Î»0, Ï0, Î»1, Ï1) {
+    var cosÏ0, cosÏ1, sinÎ»0_Î»1 = Math.sin(Î»0 - Î»1);
+    return Math.abs(sinÎ»0_Î»1) > Îµ ? Math.atan((Math.sin(Ï0) * (cosÏ1 = Math.cos(Ï1)) * Math.sin(Î»1) - Math.sin(Ï1) * (cosÏ0 = Math.cos(Ï0)) * Math.sin(Î»0)) / (cosÏ0 * cosÏ1 * sinÎ»0_Î»1)) : (Ï0 + Ï1) / 2;
+  }
+  function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) {
+    var Ï;
+    if (from == null) {
+      Ï = direction * Ï / 2;
+      listener.point(-Ï, Ï);
+      listener.point(0, Ï);
+      listener.point(Ï, Ï);
+      listener.point(Ï, 0);
+      listener.point(Ï, -Ï);
+      listener.point(0, -Ï);
+      listener.point(-Ï, -Ï);
+      listener.point(-Ï, 0);
+      listener.point(-Ï, Ï);
+    } else if (Math.abs(from[0] - to[0]) > Îµ) {
+      var s = (from[0] < to[0] ? 1 : -1) * Ï;
+      Ï = direction * s / 2;
+      listener.point(-s, Ï);
+      listener.point(0, Ï);
+      listener.point(s, Ï);
+    } else {
+      listener.point(to[0], to[1]);
+    }
+  }
+  var d3_geo_clipAntimeridianPoint = [ -Ï, 0 ];
+  function d3_geo_clipAntimeridianPolygonContains(polygon) {
+    return d3_geo_pointInPolygon(d3_geo_clipAntimeridianPoint, polygon);
+  }
+  function d3_geo_clipCircle(radius) {
+    var cr = Math.cos(radius), smallRadius = cr > 0, point = [ radius, 0 ], notHemisphere = Math.abs(cr) > Îµ, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians);
+    return d3_geo_clip(visible, clipLine, interpolate, polygonContains);
+    function visible(Î», Ï) {
+      return Math.cos(Î») * Math.cos(Ï) > cr;
+    }
+    function clipLine(listener) {
+      var point0, c0, v0, v00, clean;
+      return {
+        lineStart: function() {
+          v00 = v0 = false;
+          clean = 1;
+        },
+        point: function(Î», Ï) {
+          var point1 = [ Î», Ï ], point2, v = visible(Î», Ï), c = smallRadius ? v ? 0 : code(Î», Ï) : v ? code(Î» + (Î» < 0 ? Ï : -Ï), Ï) : 0;
+          if (!point0 && (v00 = v0 = v)) listener.lineStart();
+          if (v !== v0) {
+            point2 = intersect(point0, point1);
+            if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) {
+              point1[0] += Îµ;
+              point1[1] += Îµ;
+              v = visible(point1[0], point1[1]);
+            }
+          }
+          if (v !== v0) {
+            clean = 0;
+            if (v) {
+              listener.lineStart();
+              point2 = intersect(point1, point0);
+              listener.point(point2[0], point2[1]);
+            } else {
+              point2 = intersect(point0, point1);
+              listener.point(point2[0], point2[1]);
+              listener.lineEnd();
+            }
+            point0 = point2;
+          } else if (notHemisphere && point0 && smallRadius ^ v) {
+            var t;
+            if (!(c & c0) && (t = intersect(point1, point0, true))) {
+              clean = 0;
+              if (smallRadius) {
+                listener.lineStart();
+                listener.point(t[0][0], t[0][1]);
+                listener.point(t[1][0], t[1][1]);
+                listener.lineEnd();
+              } else {
+                listener.point(t[1][0], t[1][1]);
+                listener.lineEnd();
+                listener.lineStart();
+                listener.point(t[0][0], t[0][1]);
+              }
+            }
+          }
+          if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) {
+            listener.point(point1[0], point1[1]);
+          }
+          point0 = point1, v0 = v, c0 = c;
+        },
+        lineEnd: function() {
+          if (v0) listener.lineEnd();
+          point0 = null;
+        },
+        clean: function() {
+          return clean | (v00 && v0) << 1;
+        }
+      };
+    }
+    function intersect(a, b, two) {
+      var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b);
+      var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2;
+      if (!determinant) return !two && a;
+      var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2);
+      d3_geo_cartesianAdd(A, B);
+      var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1);
+      if (t2 < 0) return;
+      var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu);
+      d3_geo_cartesianAdd(q, A);
+      q = d3_geo_spherical(q);
+      if (!two) return q;
+      var Î»0 = a[0], Î»1 = b[0], Ï0 = a[1], Ï1 = b[1], z;
+      if (Î»1 < Î»0) z = Î»0, Î»0 = Î»1, Î»1 = z;
+      var ÎŽÎ» = Î»1 - Î»0, polar = Math.abs(ÎŽÎ» - Ï) < Îµ, meridian = polar || ÎŽÎ» < Îµ;
+      if (!polar && Ï1 < Ï0) z = Ï0, Ï0 = Ï1, Ï1 = z;
+      if (meridian ? polar ? Ï0 + Ï1 > 0 ^ q[1] < (Math.abs(q[0] - Î»0) < Îµ ? Ï0 : Ï1) : Ï0 <= q[1] && q[1] <= Ï1 : ÎŽÎ» > Ï ^ (Î»0 <= q[0] && q[0] <= Î»1)) {
+        var q1 = d3_geo_cartesianScale(u, (-w + t) / uu);
+        d3_geo_cartesianAdd(q1, A);
+        return [ q, d3_geo_spherical(q1) ];
+      }
+    }
+    function code(Î», Ï) {
+      var r = smallRadius ? radius : Ï - radius, code = 0;
+      if (Î» < -r) code |= 1; else if (Î» > r) code |= 2;
+      if (Ï < -r) code |= 4; else if (Ï > r) code |= 8;
+      return code;
+    }
+    function polygonContains(polygon) {
+      return d3_geo_pointInPolygon(point, polygon);
+    }
+  }
+  var d3_geo_clipViewMAX = 1e9;
+  function d3_geo_clipView(x0, y0, x1, y1) {
+    return function(listener) {
+      var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), segments, polygon, ring;
+      var clip = {
+        point: point,
+        lineStart: lineStart,
+        lineEnd: lineEnd,
+        polygonStart: function() {
+          listener = bufferListener;
+          segments = [];
+          polygon = [];
+        },
+        polygonEnd: function() {
+          listener = listener_;
+          if ((segments = d3.merge(segments)).length) {
+            listener.polygonStart();
+            d3_geo_clipPolygon(segments, compare, inside, interpolate, listener);
+            listener.polygonEnd();
+          } else if (insidePolygon([ x0, y0 ])) {
+            listener.polygonStart(), listener.lineStart();
+            interpolate(null, null, 1, listener);
+            listener.lineEnd(), listener.polygonEnd();
+          }
+          segments = polygon = ring = null;
+        }
+      };
+      function inside(point) {
+        var a = corner(point, -1), i = insidePolygon([ a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0 ]);
+        return i;
+      }
+      function insidePolygon(p) {
+        var wn = 0, n = polygon.length, y = p[1];
+        for (var i = 0; i < n; ++i) {
+          for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) {
+            b = v[j];
+            if (a[1] <= y) {
+              if (b[1] > y && isLeft(a, b, p) > 0) ++wn;
+            } else {
+              if (b[1] <= y && isLeft(a, b, p) < 0) --wn;
+            }
+            a = b;
+          }
+        }
+        return wn !== 0;
+      }
+      function isLeft(a, b, c) {
+        return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
+      }
+      function interpolate(from, to, direction, listener) {
+        var a = 0, a1 = 0;
+        if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) {
+          do {
+            listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0);
+          } while ((a = (a + direction + 4) % 4) !== a1);
+        } else {
+          listener.point(to[0], to[1]);
+        }
+      }
+      function visible(x, y) {
+        return x0 <= x && x <= x1 && y0 <= y && y <= y1;
+      }
+      function point(x, y) {
+        if (visible(x, y)) listener.point(x, y);
+      }
+      var x__, y__, v__, x_, y_, v_, first;
+      function lineStart() {
+        clip.point = linePoint;
+        if (polygon) polygon.push(ring = []);
+        first = true;
+        v_ = false;
+        x_ = y_ = NaN;
+      }
+      function lineEnd() {
+        if (segments) {
+          linePoint(x__, y__);
+          if (v__ && v_) bufferListener.rejoin();
+          segments.push(bufferListener.buffer());
+        }
+        clip.point = point;
+        if (v_) listener.lineEnd();
+      }
+      function linePoint(x, y) {
+        x = Math.max(-d3_geo_clipViewMAX, Math.min(d3_geo_clipViewMAX, x));
+        y = Math.max(-d3_geo_clipViewMAX, Math.min(d3_geo_clipViewMAX, y));
+        var v = visible(x, y);
+        if (polygon) ring.push([ x, y ]);
+        if (first) {
+          x__ = x, y__ = y, v__ = v;
+          first = false;
+          if (v) {
+            listener.lineStart();
+            listener.point(x, y);
+          }
+        } else {
+          if (v && v_) listener.point(x, y); else {
+            var a = [ x_, y_ ], b = [ x, y ];
+            if (clipLine(a, b)) {
+              if (!v_) {
+                listener.lineStart();
+                listener.point(a[0], a[1]);
+              }
+              listener.point(b[0], b[1]);
+              if (!v) listener.lineEnd();
+            } else if (v) {
+              listener.lineStart();
+              listener.point(x, y);
+            }
+          }
+        }
+        x_ = x, y_ = y, v_ = v;
+      }
+      return clip;
+    };
+    function corner(p, direction) {
+      return Math.abs(p[0] - x0) < Îµ ? direction > 0 ? 0 : 3 : Math.abs(p[0] - x1) < Îµ ? direction > 0 ? 2 : 1 : Math.abs(p[1] - y0) < Îµ ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2;
+    }
+    function compare(a, b) {
+      return comparePoints(a.point, b.point);
+    }
+    function comparePoints(a, b) {
+      var ca = corner(a, 1), cb = corner(b, 1);
+      return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0];
+    }
+    function clipLine(a, b) {
+      var dx = b[0] - a[0], dy = b[1] - a[1], t = [ 0, 1 ];
+      if (Math.abs(dx) < Îµ && Math.abs(dy) < Îµ) return x0 <= a[0] && a[0] <= x1 && y0 <= a[1] && a[1] <= y1;
+      if (d3_geo_clipViewT(x0 - a[0], dx, t) && d3_geo_clipViewT(a[0] - x1, -dx, t) && d3_geo_clipViewT(y0 - a[1], dy, t) && d3_geo_clipViewT(a[1] - y1, -dy, t)) {
+        if (t[1] < 1) {
+          b[0] = a[0] + t[1] * dx;
+          b[1] = a[1] + t[1] * dy;
+        }
+        if (t[0] > 0) {
+          a[0] += t[0] * dx;
+          a[1] += t[0] * dy;
+        }
+        return true;
+      }
+      return false;
+    }
+  }
+  function d3_geo_clipViewT(num, denominator, t) {
+    if (Math.abs(denominator) < Îµ) return num <= 0;
+    var u = num / denominator;
+    if (denominator > 0) {
+      if (u > t[1]) return false;
+      if (u > t[0]) t[0] = u;
+    } else {
+      if (u < t[0]) return false;
+      if (u < t[1]) t[1] = u;
+    }
+    return true;
+  }
+  function d3_geo_compose(a, b) {
+    function compose(x, y) {
+      return x = a(x, y), b(x[0], x[1]);
+    }
+    if (a.invert && b.invert) compose.invert = function(x, y) {
+      return x = b.invert(x, y), x && a.invert(x[0], x[1]);
+    };
+    return compose;
+  }
+  function d3_geo_conic(projectAt) {
+    var Ï0 = 0, Ï1 = Ï / 3, m = d3_geo_projectionMutator(projectAt), p = m(Ï0, Ï1);
+    p.parallels = function(_) {
+      if (!arguments.length) return [ Ï0 / Ï * 180, Ï1 / Ï * 180 ];
+      return m(Ï0 = _[0] * Ï / 180, Ï1 = _[1] * Ï / 180);
+    };
+    return p;
+  }
+  function d3_geo_conicEqualArea(Ï0, Ï1) {
+    var sinÏ0 = Math.sin(Ï0), n = (sinÏ0 + Math.sin(Ï1)) / 2, C = 1 + sinÏ0 * (2 * n - sinÏ0), Ï0 = Math.sqrt(C) / n;
+    function forward(Î», Ï) {
+      var Ï = Math.sqrt(C - 2 * n * Math.sin(Ï)) / n;
+      return [ Ï * Math.sin(Î» *= n), Ï0 - Ï * Math.cos(Î») ];
+    }
+    forward.invert = function(x, y) {
+      var Ï0_y = Ï0 - y;
+      return [ Math.atan2(x, Ï0_y) / n, d3_asin((C - (x * x + Ï0_y * Ï0_y) * n * n) / (2 * n)) ];
+    };
+    return forward;
+  }
+  (d3.geo.conicEqualArea = function() {
+    return d3_geo_conic(d3_geo_conicEqualArea);
+  }).raw = d3_geo_conicEqualArea;
+  d3.geo.albers = function() {
+    return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070);
+  };
+  d3.geo.albersUsa = function() {
+    var lower48 = d3.geo.albers();
+    var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]);
+    var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]);
+    var point, pointStream = {
+      point: function(x, y) {
+        point = [ x, y ];
+      }
+    }, lower48Point, alaskaPoint, hawaiiPoint;
+    function albersUsa(coordinates) {
+      var x = coordinates[0], y = coordinates[1];
+      point = null;
+      (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y);
+      return point;
+    }
+    albersUsa.invert = function(coordinates) {
+      var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k;
+      return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates);
+    };
+    albersUsa.stream = function(stream) {
+      var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream);
+      return {
+        point: function(x, y) {
+          lower48Stream.point(x, y);
+          alaskaStream.point(x, y);
+          hawaiiStream.point(x, y);
+        },
+        sphere: function() {
+          lower48Stream.sphere();
+          alaskaStream.sphere();
+          hawaiiStream.sphere();
+        },
+        lineStart: function() {
+          lower48Stream.lineStart();
+          alaskaStream.lineStart();
+          hawaiiStream.lineStart();
+        },
+        lineEnd: function() {
+          lower48Stream.lineEnd();
+          alaskaStream.lineEnd();
+          hawaiiStream.lineEnd();
+        },
+        polygonStart: function() {
+          lower48Stream.polygonStart();
+          alaskaStream.polygonStart();
+          hawaiiStream.polygonStart();
+        },
+        polygonEnd: function() {
+          lower48Stream.polygonEnd();
+          alaskaStream.polygonEnd();
+          hawaiiStream.polygonEnd();
+        }
+      };
+    };
+    albersUsa.precision = function(_) {
+      if (!arguments.length) return lower48.precision();
+      lower48.precision(_);
+      alaska.precision(_);
+      hawaii.precision(_);
+      return albersUsa;
+    };
+    albersUsa.scale = function(_) {
+      if (!arguments.length) return lower48.scale();
+      lower48.scale(_);
+      alaska.scale(_ * .35);
+      hawaii.scale(_);
+      return albersUsa.translate(lower48.translate());
+    };
+    albersUsa.translate = function(_) {
+      if (!arguments.length) return lower48.translate();
+      var k = lower48.scale(), x = +_[0], y = +_[1];
+      lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point;
+      alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + Îµ, y + .12 * k + Îµ ], [ x - .214 * k - Îµ, y + .234 * k - Îµ ] ]).stream(pointStream).point;
+      hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + Îµ, y + .166 * k + Îµ ], [ x - .115 * k - Îµ, y + .234 * k - Îµ ] ]).stream(pointStream).point;
+      return albersUsa;
+    };
+    return albersUsa.scale(1070);
+  };
+  var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {
+    point: d3_noop,
+    lineStart: d3_noop,
+    lineEnd: d3_noop,
+    polygonStart: function() {
+      d3_geo_pathAreaPolygon = 0;
+      d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart;
+    },
+    polygonEnd: function() {
+      d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop;
+      d3_geo_pathAreaSum += Math.abs(d3_geo_pathAreaPolygon / 2);
+    }
+  };
+  function d3_geo_pathAreaRingStart() {
+    var x00, y00, x0, y0;
+    d3_geo_pathArea.point = function(x, y) {
+      d3_geo_pathArea.point = nextPoint;
+      x00 = x0 = x, y00 = y0 = y;
+    };
+    function nextPoint(x, y) {
+      d3_geo_pathAreaPolygon += y0 * x - x0 * y;
+      x0 = x, y0 = y;
+    }
+    d3_geo_pathArea.lineEnd = function() {
+      nextPoint(x00, y00);
+    };
+  }
+  var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1;
+  var d3_geo_pathBounds = {
+    point: d3_geo_pathBoundsPoint,
+    lineStart: d3_noop,
+    lineEnd: d3_noop,
+    polygonStart: d3_noop,
+    polygonEnd: d3_noop
+  };
+  function d3_geo_pathBoundsPoint(x, y) {
+    if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x;
+    if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x;
+    if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y;
+    if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y;
+  }
+  function d3_geo_pathBuffer() {
+    var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = [];
+    var stream = {
+      point: point,
+      lineStart: function() {
+        stream.point = pointLineStart;
+      },
+      lineEnd: lineEnd,
+      polygonStart: function() {
+        stream.lineEnd = lineEndPolygon;
+      },
+      polygonEnd: function() {
+        stream.lineEnd = lineEnd;
+        stream.point = point;
+      },
+      pointRadius: function(_) {
+        pointCircle = d3_geo_pathBufferCircle(_);
+        return stream;
+      },
+      result: function() {
+        if (buffer.length) {
+          var result = buffer.join("");
+          buffer = [];
+          return result;
+        }
+      }
+    };
+    function point(x, y) {
+      buffer.push("M", x, ",", y, pointCircle);
+    }
+    function pointLineStart(x, y) {
+      buffer.push("M", x, ",", y);
+      stream.point = pointLine;
+    }
+    function pointLine(x, y) {
+      buffer.push("L", x, ",", y);
+    }
+    function lineEnd() {
+      stream.point = point;
+    }
+    function lineEndPolygon() {
+      buffer.push("Z");
+    }
+    return stream;
+  }
+  function d3_geo_pathBufferCircle(radius) {
+    return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z";
+  }
+  var d3_geo_pathCentroid = {
+    point: d3_geo_pathCentroidPoint,
+    lineStart: d3_geo_pathCentroidLineStart,
+    lineEnd: d3_geo_pathCentroidLineEnd,
+    polygonStart: function() {
+      d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart;
+    },
+    polygonEnd: function() {
+      d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
+      d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart;
+      d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd;
+    }
+  };
+  function d3_geo_pathCentroidPoint(x, y) {
+    d3_geo_centroidX0 += x;
+    d3_geo_centroidY0 += y;
+    ++d3_geo_centroidZ0;
+  }
+  function d3_geo_pathCentroidLineStart() {
+    var x0, y0;
+    d3_geo_pathCentroid.point = function(x, y) {
+      d3_geo_pathCentroid.point = nextPoint;
+      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
+    };
+    function nextPoint(x, y) {
+      var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
+      d3_geo_centroidX1 += z * (x0 + x) / 2;
+      d3_geo_centroidY1 += z * (y0 + y) / 2;
+      d3_geo_centroidZ1 += z;
+      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
+    }
+  }
+  function d3_geo_pathCentroidLineEnd() {
+    d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
+  }
+  function d3_geo_pathCentroidRingStart() {
+    var x00, y00, x0, y0;
+    d3_geo_pathCentroid.point = function(x, y) {
+      d3_geo_pathCentroid.point = nextPoint;
+      d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y);
+    };
+    function nextPoint(x, y) {
+      var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
+      d3_geo_centroidX1 += z * (x0 + x) / 2;
+      d3_geo_centroidY1 += z * (y0 + y) / 2;
+      d3_geo_centroidZ1 += z;
+      z = y0 * x - x0 * y;
+      d3_geo_centroidX2 += z * (x0 + x);
+      d3_geo_centroidY2 += z * (y0 + y);
+      d3_geo_centroidZ2 += z * 3;
+      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
+    }
+    d3_geo_pathCentroid.lineEnd = function() {
+      nextPoint(x00, y00);
+    };
+  }
+  function d3_geo_pathContext(context) {
+    var pointRadius = 4.5;
+    var stream = {
+      point: point,
+      lineStart: function() {
+        stream.point = pointLineStart;
+      },
+      lineEnd: lineEnd,
+      polygonStart: function() {
+        stream.lineEnd = lineEndPolygon;
+      },
+      polygonEnd: function() {
+        stream.lineEnd = lineEnd;
+        stream.point = point;
+      },
+      pointRadius: function(_) {
+        pointRadius = _;
+        return stream;
+      },
+      result: d3_noop
+    };
+    function point(x, y) {
+      context.moveTo(x, y);
+      context.arc(x, y, pointRadius, 0, 2 * Ï);
+    }
+    function pointLineStart(x, y) {
+      context.moveTo(x, y);
+      stream.point = pointLine;
+    }
+    function pointLine(x, y) {
+      context.lineTo(x, y);
+    }
+    function lineEnd() {
+      stream.point = point;
+    }
+    function lineEndPolygon() {
+      context.closePath();
+    }
+    return stream;
+  }
+  function d3_geo_resample(project) {
+    var ÎŽ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16;
+    function resample(stream) {
+      var Î»00, Ï00, x00, y00, a00, b00, c00, Î»0, x0, y0, a0, b0, c0;
+      var resample = {
+        point: point,
+        lineStart: lineStart,
+        lineEnd: lineEnd,
+        polygonStart: function() {
+          stream.polygonStart();
+          resample.lineStart = ringStart;
+        },
+        polygonEnd: function() {
+          stream.polygonEnd();
+          resample.lineStart = lineStart;
+        }
+      };
+      function point(x, y) {
+        x = project(x, y);
+        stream.point(x[0], x[1]);
+      }
+      function lineStart() {
+        x0 = NaN;
+        resample.point = linePoint;
+        stream.lineStart();
+      }
+      function linePoint(Î», Ï) {
+        var c = d3_geo_cartesian([ Î», Ï ]), p = project(Î», Ï);
+        resampleLineTo(x0, y0, Î»0, a0, b0, c0, x0 = p[0], y0 = p[1], Î»0 = Î», a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream);
+        stream.point(x0, y0);
+      }
+      function lineEnd() {
+        resample.point = point;
+        stream.lineEnd();
+      }
+      function ringStart() {
+        lineStart();
+        resample.point = ringPoint;
+        resample.lineEnd = ringEnd;
+      }
+      function ringPoint(Î», Ï) {
+        linePoint(Î»00 = Î», Ï00 = Ï), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
+        resample.point = linePoint;
+      }
+      function ringEnd() {
+        resampleLineTo(x0, y0, Î»0, a0, b0, c0, x00, y00, Î»00, a00, b00, c00, maxDepth, stream);
+        resample.lineEnd = lineEnd;
+        lineEnd();
+      }
+      return resample;
+    }
+    function resampleLineTo(x0, y0, Î»0, a0, b0, c0, x1, y1, Î»1, a1, b1, c1, depth, stream) {
+      var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy;
+      if (d2 > 4 * ÎŽ2 && depth--) {
+        var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), Ï2 = Math.asin(c /= m), Î»2 = Math.abs(Math.abs(c) - 1) < Îµ ? (Î»0 + Î»1) / 2 : Math.atan2(b, a), p = project(Î»2, Ï2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2;
+        if (dz * dz / d2 > ÎŽ2 || Math.abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) {
+          resampleLineTo(x0, y0, Î»0, a0, b0, c0, x2, y2, Î»2, a /= m, b /= m, c, depth, stream);
+          stream.point(x2, y2);
+          resampleLineTo(x2, y2, Î»2, a, b, c, x1, y1, Î»1, a1, b1, c1, depth, stream);
+        }
+      }
+    }
+    resample.precision = function(_) {
+      if (!arguments.length) return Math.sqrt(ÎŽ2);
+      maxDepth = (ÎŽ2 = _ * _) > 0 && 16;
+      return resample;
+    };
+    return resample;
+  }
+  d3.geo.path = function() {
+    var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream;
+    function path(object) {
+      if (object) {
+        if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments));
+        if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream);
+        d3.geo.stream(object, cacheStream);
+      }
+      return contextStream.result();
+    }
+    path.area = function(object) {
+      d3_geo_pathAreaSum = 0;
+      d3.geo.stream(object, projectStream(d3_geo_pathArea));
+      return d3_geo_pathAreaSum;
+    };
+    path.centroid = function(object) {
+      d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
+      d3.geo.stream(object, projectStream(d3_geo_pathCentroid));
+      return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ];
+    };
+    path.bounds = function(object) {
+      d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity);
+      d3.geo.stream(object, projectStream(d3_geo_pathBounds));
+      return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ];
+    };
+    path.projection = function(_) {
+      if (!arguments.length) return projection;
+      projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity;
+      return reset();
+    };
+    path.context = function(_) {
+      if (!arguments.length) return context;
+      contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_);
+      if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius);
+      return reset();
+    };
+    path.pointRadius = function(_) {
+      if (!arguments.length) return pointRadius;
+      pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_);
+      return path;
+    };
+    function reset() {
+      cacheStream = null;
+      return path;
+    }
+    return path.projection(d3.geo.albersUsa()).context(null);
+  };
+  function d3_geo_pathProjectStream(project) {
+    var resample = d3_geo_resample(function(Î», Ï) {
+      return project([ Î» * d3_degrees, Ï * d3_degrees ]);
+    });
+    return function(stream) {
+      stream = resample(stream);
+      return {
+        point: function(Î», Ï) {
+          stream.point(Î» * d3_radians, Ï * d3_radians);
+        },
+        sphere: function() {
+          stream.sphere();
+        },
+        lineStart: function() {
+          stream.lineStart();
+        },
+        lineEnd: function() {
+          stream.lineEnd();
+        },
+        polygonStart: function() {
+          stream.polygonStart();
+        },
+        polygonEnd: function() {
+          stream.polygonEnd();
+        }
+      };
+    };
+  }
+  d3.geo.projection = d3_geo_projection;
+  d3.geo.projectionMutator = d3_geo_projectionMutator;
+  function d3_geo_projection(project) {
+    return d3_geo_projectionMutator(function() {
+      return project;
+    })();
+  }
+  function d3_geo_projectionMutator(projectAt) {
+    var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) {
+      x = project(x, y);
+      return [ x[0] * k + ÎŽx, ÎŽy - x[1] * k ];
+    }), k = 150, x = 480, y = 250, Î» = 0, Ï = 0, ÎŽÎ» = 0, ÎŽÏ = 0, ÎŽÎ³ = 0, ÎŽx, ÎŽy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream;
+    function projection(point) {
+      point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
+      return [ point[0] * k + ÎŽx, ÎŽy - point[1] * k ];
+    }
+    function invert(point) {
+      point = projectRotate.invert((point[0] - ÎŽx) / k, (ÎŽy - point[1]) / k);
+      return point && [ point[0] * d3_degrees, point[1] * d3_degrees ];
+    }
+    projection.stream = function(output) {
+      if (stream) stream.valid = false;
+      stream = d3_geo_projectionRadiansRotate(rotate, preclip(projectResample(postclip(output))));
+      stream.valid = true;
+      return stream;
+    };
+    projection.clipAngle = function(_) {
+      if (!arguments.length) return clipAngle;
+      preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians);
+      return invalidate();
+    };
+    projection.clipExtent = function(_) {
+      if (!arguments.length) return clipExtent;
+      clipExtent = _;
+      postclip = _ == null ? d3_identity : d3_geo_clipView(_[0][0], _[0][1], _[1][0], _[1][1]);
+      return invalidate();
+    };
+    projection.scale = function(_) {
+      if (!arguments.length) return k;
+      k = +_;
+      return reset();
+    };
+    projection.translate = function(_) {
+      if (!arguments.length) return [ x, y ];
+      x = +_[0];
+      y = +_[1];
+      return reset();
+    };
+    projection.center = function(_) {
+      if (!arguments.length) return [ Î» * d3_degrees, Ï * d3_degrees ];
+      Î» = _[0] % 360 * d3_radians;
+      Ï = _[1] % 360 * d3_radians;
+      return reset();
+    };
+    projection.rotate = function(_) {
+      if (!arguments.length) return [ ÎŽÎ» * d3_degrees, ÎŽÏ * d3_degrees, ÎŽÎ³ * d3_degrees ];
+      ÎŽÎ» = _[0] % 360 * d3_radians;
+      ÎŽÏ = _[1] % 360 * d3_radians;
+      ÎŽÎ³ = _.length > 2 ? _[2] % 360 * d3_radians : 0;
+      return reset();
+    };
+    d3.rebind(projection, projectResample, "precision");
+    function reset() {
+      projectRotate = d3_geo_compose(rotate = d3_geo_rotation(ÎŽÎ», ÎŽÏ, ÎŽÎ³), project);
+      var center = project(Î», Ï);
+      ÎŽx = x - center[0] * k;
+      ÎŽy = y + center[1] * k;
+      return invalidate();
+    }
+    function invalidate() {
+      if (stream) {
+        stream.valid = false;
+        stream = null;
+      }
+      return projection;
+    }
+    return function() {
+      project = projectAt.apply(this, arguments);
+      projection.invert = project.invert && invert;
+      return reset();
+    };
+  }
+  function d3_geo_projectionRadiansRotate(rotate, stream) {
+    return {
+      point: function(x, y) {
+        y = rotate(x * d3_radians, y * d3_radians), x = y[0];
+        stream.point(x > Ï ? x - 2 * Ï : x < -Ï ? x + 2 * Ï : x, y[1]);
+      },
+      sphere: function() {
+        stream.sphere();
+      },
+      lineStart: function() {
+        stream.lineStart();
+      },
+      lineEnd: function() {
+        stream.lineEnd();
+      },
+      polygonStart: function() {
+        stream.polygonStart();
+      },
+      polygonEnd: function() {
+        stream.polygonEnd();
+      }
+    };
+  }
+  function d3_geo_equirectangular(Î», Ï) {
+    return [ Î», Ï ];
+  }
+  (d3.geo.equirectangular = function() {
+    return d3_geo_projection(d3_geo_equirectangular);
+  }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular;
+  d3.geo.rotation = function(rotate) {
+    rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0);
+    function forward(coordinates) {
+      coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
+      return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
+    }
+    forward.invert = function(coordinates) {
+      coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
+      return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
+    };
+    return forward;
+  };
+  function d3_geo_rotation(ÎŽÎ», ÎŽÏ, ÎŽÎ³) {
+    return ÎŽÎ» ? ÎŽÏ || ÎŽÎ³ ? d3_geo_compose(d3_geo_rotationÎ»(ÎŽÎ»), d3_geo_rotationÏÎ³(ÎŽÏ, ÎŽÎ³)) : d3_geo_rotationÎ»(ÎŽÎ») : ÎŽÏ || ÎŽÎ³ ? d3_geo_rotationÏÎ³(ÎŽÏ, ÎŽÎ³) : d3_geo_equirectangular;
+  }
+  function d3_geo_forwardRotationÎ»(ÎŽÎ») {
+    return function(Î», Ï) {
+      return Î» += ÎŽÎ», [ Î» > Ï ? Î» - 2 * Ï : Î» < -Ï ? Î» + 2 * Ï : Î», Ï ];
+    };
+  }
+  function d3_geo_rotationÎ»(ÎŽÎ») {
+    var rotation = d3_geo_forwardRotationÎ»(ÎŽÎ»);
+    rotation.invert = d3_geo_forwardRotationÎ»(-ÎŽÎ»);
+    return rotation;
+  }
+  function d3_geo_rotationÏÎ³(ÎŽÏ, ÎŽÎ³) {
+    var cosÎŽÏ = Math.cos(ÎŽÏ), sinÎŽÏ = Math.sin(ÎŽÏ), cosÎŽÎ³ = Math.cos(ÎŽÎ³), sinÎŽÎ³ = Math.sin(ÎŽÎ³);
+    function rotation(Î», Ï) {
+      var cosÏ = Math.cos(Ï), x = Math.cos(Î») * cosÏ, y = Math.sin(Î») * cosÏ, z = Math.sin(Ï), k = z * cosÎŽÏ + x * sinÎŽÏ;
+      return [ Math.atan2(y * cosÎŽÎ³ - k * sinÎŽÎ³, x * cosÎŽÏ - z * sinÎŽÏ), d3_asin(k * cosÎŽÎ³ + y * sinÎŽÎ³) ];
+    }
+    rotation.invert = function(Î», Ï) {
+      var cosÏ = Math.cos(Ï), x = Math.cos(Î») * cosÏ, y = Math.sin(Î») * cosÏ, z = Math.sin(Ï), k = z * cosÎŽÎ³ - y * sinÎŽÎ³;
+      return [ Math.atan2(y * cosÎŽÎ³ + z * sinÎŽÎ³, x * cosÎŽÏ + k * sinÎŽÏ), d3_asin(k * cosÎŽÏ - x * sinÎŽÏ) ];
+    };
+    return rotation;
+  }
+  d3.geo.circle = function() {
+    var origin = [ 0, 0 ], angle, precision = 6, interpolate;
+    function circle() {
+      var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = [];
+      interpolate(null, null, 1, {
+        point: function(x, y) {
+          ring.push(x = rotate(x, y));
+          x[0] *= d3_degrees, x[1] *= d3_degrees;
+        }
+      });
+      return {
+        type: "Polygon",
+        coordinates: [ ring ]
+      };
+    }
+    circle.origin = function(x) {
+      if (!arguments.length) return origin;
+      origin = x;
+      return circle;
+    };
+    circle.angle = function(x) {
+      if (!arguments.length) return angle;
+      interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians);
+      return circle;
+    };
+    circle.precision = function(_) {
+      if (!arguments.length) return precision;
+      interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians);
+      return circle;
+    };
+    return circle.angle(90);
+  };
+  function d3_geo_circleInterpolate(radius, precision) {
+    var cr = Math.cos(radius), sr = Math.sin(radius);
+    return function(from, to, direction, listener) {
+      if (from != null) {
+        from = d3_geo_circleAngle(cr, from);
+        to = d3_geo_circleAngle(cr, to);
+        if (direction > 0 ? from < to : from > to) from += direction * 2 * Ï;
+      } else {
+        from = radius + direction * 2 * Ï;
+        to = radius;
+      }
+      var point;
+      for (var step = direction * precision, t = from; direction > 0 ? t > to : t < to; t -= step) {
+        listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]);
+      }
+    };
+  }
+  function d3_geo_circleAngle(cr, point) {
+    var a = d3_geo_cartesian(point);
+    a[0] -= cr;
+    d3_geo_cartesianNormalize(a);
+    var angle = d3_acos(-a[1]);
+    return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - Îµ) % (2 * Math.PI);
+  }
+  d3.geo.distance = function(a, b) {
+    var ÎÎ» = (b[0] - a[0]) * d3_radians, Ï0 = a[1] * d3_radians, Ï1 = b[1] * d3_radians, sinÎÎ» = Math.sin(ÎÎ»), cosÎÎ» = Math.cos(ÎÎ»), sinÏ0 = Math.sin(Ï0), cosÏ0 = Math.cos(Ï0), sinÏ1 = Math.sin(Ï1), cosÏ1 = Math.cos(Ï1), t;
+    return Math.atan2(Math.sqrt((t = cosÏ1 * sinÎÎ») * t + (t = cosÏ0 * sinÏ1 - sinÏ0 * cosÏ1 * cosÎÎ») * t), sinÏ0 * sinÏ1 + cosÏ0 * cosÏ1 * cosÎÎ»);
+  };
+  d3.geo.graticule = function() {
+    var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5;
+    function graticule() {
+      return {
+        type: "MultiLineString",
+        coordinates: lines()
+      };
+    }
+    function lines() {
+      return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) {
+        return Math.abs(x % DX) > Îµ;
+      }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) {
+        return Math.abs(y % DY) > Îµ;
+      }).map(y));
+    }
+    graticule.lines = function() {
+      return lines().map(function(coordinates) {
+        return {
+          type: "LineString",
+          coordinates: coordinates
+        };
+      });
+    };
+    graticule.outline = function() {
+      return {
+        type: "Polygon",
+        coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ]
+      };
+    };
+    graticule.extent = function(_) {
+      if (!arguments.length) return graticule.minorExtent();
+      return graticule.majorExtent(_).minorExtent(_);
+    };
+    graticule.majorExtent = function(_) {
+      if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ];
+      X0 = +_[0][0], X1 = +_[1][0];
+      Y0 = +_[0][1], Y1 = +_[1][1];
+      if (X0 > X1) _ = X0, X0 = X1, X1 = _;
+      if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _;
+      return graticule.precision(precision);
+    };
+    graticule.minorExtent = function(_) {
+      if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
+      x0 = +_[0][0], x1 = +_[1][0];
+      y0 = +_[0][1], y1 = +_[1][1];
+      if (x0 > x1) _ = x0, x0 = x1, x1 = _;
+      if (y0 > y1) _ = y0, y0 = y1, y1 = _;
+      return graticule.precision(precision);
+    };
+    graticule.step = function(_) {
+      if (!arguments.length) return graticule.minorStep();
+      return graticule.majorStep(_).minorStep(_);
+    };
+    graticule.majorStep = function(_) {
+      if (!arguments.length) return [ DX, DY ];
+      DX = +_[0], DY = +_[1];
+      return graticule;
+    };
+    graticule.minorStep = function(_) {
+      if (!arguments.length) return [ dx, dy ];
+      dx = +_[0], dy = +_[1];
+      return graticule;
+    };
+    graticule.precision = function(_) {
+      if (!arguments.length) return precision;
+      precision = +_;
+      x = d3_geo_graticuleX(y0, y1, 90);
+      y = d3_geo_graticuleY(x0, x1, precision);
+      X = d3_geo_graticuleX(Y0, Y1, 90);
+      Y = d3_geo_graticuleY(X0, X1, precision);
+      return graticule;
+    };
+    return graticule.majorExtent([ [ -180, -90 + Îµ ], [ 180, 90 - Îµ ] ]).minorExtent([ [ -180, -80 - Îµ ], [ 180, 80 + Îµ ] ]);
+  };
+  function d3_geo_graticuleX(y0, y1, dy) {
+    var y = d3.range(y0, y1 - Îµ, dy).concat(y1);
+    return function(x) {
+      return y.map(function(y) {
+        return [ x, y ];
+      });
+    };
+  }
+  function d3_geo_graticuleY(x0, x1, dx) {
+    var x = d3.range(x0, x1 - Îµ, dx).concat(x1);
+    return function(y) {
+      return x.map(function(x) {
+        return [ x, y ];
+      });
+    };
+  }
+  function d3_source(d) {
+    return d.source;
+  }
+  function d3_target(d) {
+    return d.target;
+  }
+  d3.geo.greatArc = function() {
+    var source = d3_source, source_, target = d3_target, target_;
+    function greatArc() {
+      return {
+        type: "LineString",
+        coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ]
+      };
+    }
+    greatArc.distance = function() {
+      return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments));
+    };
+    greatArc.source = function(_) {
+      if (!arguments.length) return source;
+      source = _, source_ = typeof _ === "function" ? null : _;
+      return greatArc;
+    };
+    greatArc.target = function(_) {
+      if (!arguments.length) return target;
+      target = _, target_ = typeof _ === "function" ? null : _;
+      return greatArc;
+    };
+    greatArc.precision = function() {
+      return arguments.length ? greatArc : 0;
+    };
+    return greatArc;
+  };
+  d3.geo.interpolate = function(source, target) {
+    return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians);
+  };
+  function d3_geo_interpolate(x0, y0, x1, y1) {
+    var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d);
+    var interpolate = d ? function(t) {
+      var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1;
+      return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ];
+    } : function() {
+      return [ x0 * d3_degrees, y0 * d3_degrees ];
+    };
+    interpolate.distance = d;
+    return interpolate;
+  }
+  d3.geo.length = function(object) {
+    d3_geo_lengthSum = 0;
+    d3.geo.stream(object, d3_geo_length);
+    return d3_geo_lengthSum;
+  };
+  var d3_geo_lengthSum;
+  var d3_geo_length = {
+    sphere: d3_noop,
+    point: d3_noop,
+    lineStart: d3_geo_lengthLineStart,
+    lineEnd: d3_noop,
+    polygonStart: d3_noop,
+    polygonEnd: d3_noop
+  };
+  function d3_geo_lengthLineStart() {
+    var Î»0, sinÏ0, cosÏ0;
+    d3_geo_length.point = function(Î», Ï) {
+      Î»0 = Î» * d3_radians, sinÏ0 = Math.sin(Ï *= d3_radians), cosÏ0 = Math.cos(Ï);
+      d3_geo_length.point = nextPoint;
+    };
+    d3_geo_length.lineEnd = function() {
+      d3_geo_length.point = d3_geo_length.lineEnd = d3_noop;
+    };
+    function nextPoint(Î», Ï) {
+      var sinÏ = Math.sin(Ï *= d3_radians), cosÏ = Math.cos(Ï), t = Math.abs((Î» *= d3_radians) - Î»0), cosÎÎ» = Math.cos(t);
+      d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosÏ * Math.sin(t)) * t + (t = cosÏ0 * sinÏ - sinÏ0 * cosÏ * cosÎÎ») * t), sinÏ0 * sinÏ + cosÏ0 * cosÏ * cosÎÎ»);
+      Î»0 = Î», sinÏ0 = sinÏ, cosÏ0 = cosÏ;
+    }
+  }
+  function d3_geo_azimuthal(scale, angle) {
+    function azimuthal(Î», Ï) {
+      var cosÎ» = Math.cos(Î»), cosÏ = Math.cos(Ï), k = scale(cosÎ» * cosÏ);
+      return [ k * cosÏ * Math.sin(Î»), k * Math.sin(Ï) ];
+    }
+    azimuthal.invert = function(x, y) {
+      var Ï = Math.sqrt(x * x + y * y), c = angle(Ï), sinc = Math.sin(c), cosc = Math.cos(c);
+      return [ Math.atan2(x * sinc, Ï * cosc), Math.asin(Ï && y * sinc / Ï) ];
+    };
+    return azimuthal;
+  }
+  var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosÎ»cosÏ) {
+    return Math.sqrt(2 / (1 + cosÎ»cosÏ));
+  }, function(Ï) {
+    return 2 * Math.asin(Ï / 2);
+  });
+  (d3.geo.azimuthalEqualArea = function() {
+    return d3_geo_projection(d3_geo_azimuthalEqualArea);
+  }).raw = d3_geo_azimuthalEqualArea;
+  var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosÎ»cosÏ) {
+    var c = Math.acos(cosÎ»cosÏ);
+    return c && c / Math.sin(c);
+  }, d3_identity);
+  (d3.geo.azimuthalEquidistant = function() {
+    return d3_geo_projection(d3_geo_azimuthalEquidistant);
+  }).raw = d3_geo_azimuthalEquidistant;
+  function d3_geo_conicConformal(Ï0, Ï1) {
+    var cosÏ0 = Math.cos(Ï0), t = function(Ï) {
+      return Math.tan(Ï / 4 + Ï / 2);
+    }, n = Ï0 === Ï1 ? Math.sin(Ï0) : Math.log(cosÏ0 / Math.cos(Ï1)) / Math.log(t(Ï1) / t(Ï0)), F = cosÏ0 * Math.pow(t(Ï0), n) / n;
+    if (!n) return d3_geo_mercator;
+    function forward(Î», Ï) {
+      var Ï = Math.abs(Math.abs(Ï) - Ï / 2) < Îµ ? 0 : F / Math.pow(t(Ï), n);
+      return [ Ï * Math.sin(n * Î»), F - Ï * Math.cos(n * Î») ];
+    }
+    forward.invert = function(x, y) {
+      var Ï0_y = F - y, Ï = d3_sgn(n) * Math.sqrt(x * x + Ï0_y * Ï0_y);
+      return [ Math.atan2(x, Ï0_y) / n, 2 * Math.atan(Math.pow(F / Ï, 1 / n)) - Ï / 2 ];
+    };
+    return forward;
+  }
+  (d3.geo.conicConformal = function() {
+    return d3_geo_conic(d3_geo_conicConformal);
+  }).raw = d3_geo_conicConformal;
+  function d3_geo_conicEquidistant(Ï0, Ï1) {
+    var cosÏ0 = Math.cos(Ï0), n = Ï0 === Ï1 ? Math.sin(Ï0) : (cosÏ0 - Math.cos(Ï1)) / (Ï1 - Ï0), G = cosÏ0 / n + Ï0;
+    if (Math.abs(n) < Îµ) return d3_geo_equirectangular;
+    function forward(Î», Ï) {
+      var Ï = G - Ï;
+      return [ Ï * Math.sin(n * Î»), G - Ï * Math.cos(n * Î») ];
+    }
+    forward.invert = function(x, y) {
+      var Ï0_y = G - y;
+      return [ Math.atan2(x, Ï0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + Ï0_y * Ï0_y) ];
+    };
+    return forward;
+  }
+  (d3.geo.conicEquidistant = function() {
+    return d3_geo_conic(d3_geo_conicEquidistant);
+  }).raw = d3_geo_conicEquidistant;
+  var d3_geo_gnomonic = d3_geo_azimuthal(function(cosÎ»cosÏ) {
+    return 1 / cosÎ»cosÏ;
+  }, Math.atan);
+  (d3.geo.gnomonic = function() {
+    return d3_geo_projection(d3_geo_gnomonic);
+  }).raw = d3_geo_gnomonic;
+  function d3_geo_mercator(Î», Ï) {
+    return [ Î», Math.log(Math.tan(Ï / 4 + Ï / 2)) ];
+  }
+  d3_geo_mercator.invert = function(x, y) {
+    return [ x, 2 * Math.atan(Math.exp(y)) - Ï / 2 ];
+  };
+  function d3_geo_mercatorProjection(project) {
+    var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto;
+    m.scale = function() {
+      var v = scale.apply(m, arguments);
+      return v === m ? clipAuto ? m.clipExtent(null) : m : v;
+    };
+    m.translate = function() {
+      var v = translate.apply(m, arguments);
+      return v === m ? clipAuto ? m.clipExtent(null) : m : v;
+    };
+    m.clipExtent = function(_) {
+      var v = clipExtent.apply(m, arguments);
+      if (v === m) {
+        if (clipAuto = _ == null) {
+          var k = Ï * scale(), t = translate();
+          clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]);
+        }
+      } else if (clipAuto) {
+        v = null;
+      }
+      return v;
+    };
+    return m.clipExtent(null);
+  }
+  (d3.geo.mercator = function() {
+    return d3_geo_mercatorProjection(d3_geo_mercator);
+  }).raw = d3_geo_mercator;
+  var d3_geo_orthographic = d3_geo_azimuthal(function() {
+    return 1;
+  }, Math.asin);
+  (d3.geo.orthographic = function() {
+    return d3_geo_projection(d3_geo_orthographic);
+  }).raw = d3_geo_orthographic;
+  var d3_geo_stereographic = d3_geo_azimuthal(function(cosÎ»cosÏ) {
+    return 1 / (1 + cosÎ»cosÏ);
+  }, function(Ï) {
+    return 2 * Math.atan(Ï);
+  });
+  (d3.geo.stereographic = function() {
+    return d3_geo_projection(d3_geo_stereographic);
+  }).raw = d3_geo_stereographic;
+  function d3_geo_transverseMercator(Î», Ï) {
+    var B = Math.cos(Ï) * Math.sin(Î»);
+    return [ Math.log((1 + B) / (1 - B)) / 2, Math.atan2(Math.tan(Ï), Math.cos(Î»)) ];
+  }
+  d3_geo_transverseMercator.invert = function(x, y) {
+    return [ Math.atan2(d3_sinh(x), Math.cos(y)), d3_asin(Math.sin(y) / d3_cosh(x)) ];
+  };
+  (d3.geo.transverseMercator = function() {
+    return d3_geo_mercatorProjection(d3_geo_transverseMercator);
+  }).raw = d3_geo_transverseMercator;
+  d3.geom = {};
+  d3.svg = {};
+  function d3_svg_line(projection) {
+    var x = d3_svg_lineX, y = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7;
+    function line(data) {
+      var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y);
+      function segment() {
+        segments.push("M", interpolate(projection(points), tension));
+      }
+      while (++i < n) {
+        if (defined.call(this, d = data[i], i)) {
+          points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]);
+        } else if (points.length) {
+          segment();
+          points = [];
+        }
+      }
+      if (points.length) segment();
+      return segments.length ? segments.join("") : null;
+    }
+    line.x = function(_) {
+      if (!arguments.length) return x;
+      x = _;
+      return line;
+    };
+    line.y = function(_) {
+      if (!arguments.length) return y;
+      y = _;
+      return line;
+    };
+    line.defined = function(_) {
+      if (!arguments.length) return defined;
+      defined = _;
+      return line;
+    };
+    line.interpolate = function(_) {
+      if (!arguments.length) return interpolateKey;
+      if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
+      return line;
+    };
+    line.tension = function(_) {
+      if (!arguments.length) return tension;
+      tension = _;
+      return line;
+    };
+    return line;
+  }
+  d3.svg.line = function() {
+    return d3_svg_line(d3_identity);
+  };
+  function d3_svg_lineX(d) {
+    return d[0];
+  }
+  function d3_svg_lineY(d) {
+    return d[1];
+  }
+  var d3_svg_lineInterpolators = d3.map({
+    linear: d3_svg_lineLinear,
+    "linear-closed": d3_svg_lineLinearClosed,
+    step: d3_svg_lineStep,
+    "step-before": d3_svg_lineStepBefore,
+    "step-after": d3_svg_lineStepAfter,
+    basis: d3_svg_lineBasis,
+    "basis-open": d3_svg_lineBasisOpen,
+    "basis-closed": d3_svg_lineBasisClosed,
+    bundle: d3_svg_lineBundle,
+    cardinal: d3_svg_lineCardinal,
+    "cardinal-open": d3_svg_lineCardinalOpen,
+    "cardinal-closed": d3_svg_lineCardinalClosed,
+    monotone: d3_svg_lineMonotone
+  });
+  d3_svg_lineInterpolators.forEach(function(key, value) {
+    value.key = key;
+    value.closed = /-closed$/.test(key);
+  });
+  function d3_svg_lineLinear(points) {
+    return points.join("L");
+  }
+  function d3_svg_lineLinearClosed(points) {
+    return d3_svg_lineLinear(points) + "Z";
+  }
+  function d3_svg_lineStep(points) {
+    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
+    while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]);
+    if (n > 1) path.push("H", p[0]);
+    return path.join("");
+  }
+  function d3_svg_lineStepBefore(points) {
+    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
+    while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]);
+    return path.join("");
+  }
+  function d3_svg_lineStepAfter(points) {
+    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
+    while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]);
+    return path.join("");
+  }
+  function d3_svg_lineCardinalOpen(points, tension) {
+    return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), d3_svg_lineCardinalTangents(points, tension));
+  }
+  function d3_svg_lineCardinalClosed(points, tension) {
+    return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), 
+    points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension));
+  }
+  function d3_svg_lineCardinal(points, tension) {
+    return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension));
+  }
+  function d3_svg_lineHermite(points, tangents) {
+    if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) {
+      return d3_svg_lineLinear(points);
+    }
+    var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1;
+    if (quad) {
+      path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1];
+      p0 = points[1];
+      pi = 2;
+    }
+    if (tangents.length > 1) {
+      t = tangents[1];
+      p = points[pi];
+      pi++;
+      path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
+      for (var i = 2; i < tangents.length; i++, pi++) {
+        p = points[pi];
+        t = tangents[i];
+        path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
+      }
+    }
+    if (quad) {
+      var lp = points[pi];
+      path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1];
+    }
+    return path;
+  }
+  function d3_svg_lineCardinalTangents(points, tension) {
+    var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length;
+    while (++i < n) {
+      p0 = p1;
+      p1 = p2;
+      p2 = points[i];
+      tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]);
+    }
+    return tangents;
+  }
+  function d3_svg_lineBasis(points) {
+    if (points.length < 3) return d3_svg_lineLinear(points);
+    var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
+    points.push(points[n - 1]);
+    while (++i <= n) {
+      pi = points[i];
+      px.shift();
+      px.push(pi[0]);
+      py.shift();
+      py.push(pi[1]);
+      d3_svg_lineBasisBezier(path, px, py);
+    }
+    points.pop();
+    path.push("L", pi);
+    return path.join("");
+  }
+  function d3_svg_lineBasisOpen(points) {
+    if (points.length < 4) return d3_svg_lineLinear(points);
+    var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ];
+    while (++i < 3) {
+      pi = points[i];
+      px.push(pi[0]);
+      py.push(pi[1]);
+    }
+    path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py));
+    --i;
+    while (++i < n) {
+      pi = points[i];
+      px.shift();
+      px.push(pi[0]);
+      py.shift();
+      py.push(pi[1]);
+      d3_svg_lineBasisBezier(path, px, py);
+    }
+    return path.join("");
+  }
+  function d3_svg_lineBasisClosed(points) {
+    var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = [];
+    while (++i < 4) {
+      pi = points[i % n];
+      px.push(pi[0]);
+      py.push(pi[1]);
+    }
+    path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
+    --i;
+    while (++i < m) {
+      pi = points[i % n];
+      px.shift();
+      px.push(pi[0]);
+      py.shift();
+      py.push(pi[1]);
+      d3_svg_lineBasisBezier(path, px, py);
+    }
+    return path.join("");
+  }
+  function d3_svg_lineBundle(points, tension) {
+    var n = points.length - 1;
+    if (n) {
+      var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t;
+      while (++i <= n) {
+        p = points[i];
+        t = i / n;
+        p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx);
+        p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy);
+      }
+    }
+    return d3_svg_lineBasis(points);
+  }
+  function d3_svg_lineDot4(a, b) {
+    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
+  }
+  var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ];
+  function d3_svg_lineBasisBezier(path, x, y) {
+    path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y));
+  }
+  function d3_svg_lineSlope(p0, p1) {
+    return (p1[1] - p0[1]) / (p1[0] - p0[0]);
+  }
+  function d3_svg_lineFiniteDifferences(points) {
+    var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1);
+    while (++i < j) {
+      m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
+    }
+    m[i] = d;
+    return m;
+  }
+  function d3_svg_lineMonotoneTangents(points) {
+    var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1;
+    while (++i < j) {
+      d = d3_svg_lineSlope(points[i], points[i + 1]);
+      if (Math.abs(d) < 1e-6) {
+        m[i] = m[i + 1] = 0;
+      } else {
+        a = m[i] / d;
+        b = m[i + 1] / d;
+        s = a * a + b * b;
+        if (s > 9) {
+          s = d * 3 / Math.sqrt(s);
+          m[i] = s * a;
+          m[i + 1] = s * b;
+        }
+      }
+    }
+    i = -1;
+    while (++i <= j) {
+      s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i]));
+      tangents.push([ s || 0, m[i] * s || 0 ]);
+    }
+    return tangents;
+  }
+  function d3_svg_lineMonotone(points) {
+    return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points));
+  }
+  d3.geom.hull = function(vertices) {
+    var x = d3_svg_lineX, y = d3_svg_lineY;
+    if (arguments.length) return hull(vertices);
+    function hull(data) {
+      if (data.length < 3) return [];
+      var fx = d3_functor(x), fy = d3_functor(y), n = data.length, vertices, plen = n - 1, points = [], stack = [], d, i, j, h = 0, x1, y1, x2, y2, u, v, a, sp;
+      if (fx === d3_svg_lineX && y === d3_svg_lineY) vertices = data; else for (i = 0, 
+      vertices = []; i < n; ++i) {
+        vertices.push([ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ]);
+      }
+      for (i = 1; i < n; ++i) {
+        if (vertices[i][1] < vertices[h][1] || vertices[i][1] == vertices[h][1] && vertices[i][0] < vertices[h][0]) h = i;
+      }
+      for (i = 0; i < n; ++i) {
+        if (i === h) continue;
+        y1 = vertices[i][1] - vertices[h][1];
+        x1 = vertices[i][0] - vertices[h][0];
+        points.push({
+          angle: Math.atan2(y1, x1),
+          index: i
+        });
+      }
+      points.sort(function(a, b) {
+        return a.angle - b.angle;
+      });
+      a = points[0].angle;
+      v = points[0].index;
+      u = 0;
+      for (i = 1; i < plen; ++i) {
+        j = points[i].index;
+        if (a == points[i].angle) {
+          x1 = vertices[v][0] - vertices[h][0];
+          y1 = vertices[v][1] - vertices[h][1];
+          x2 = vertices[j][0] - vertices[h][0];
+          y2 = vertices[j][1] - vertices[h][1];
+          if (x1 * x1 + y1 * y1 >= x2 * x2 + y2 * y2) {
+            points[i].index = -1;
+            continue;
+          } else {
+            points[u].index = -1;
+          }
+        }
+        a = points[i].angle;
+        u = i;
+        v = j;
+      }
+      stack.push(h);
+      for (i = 0, j = 0; i < 2; ++j) {
+        if (points[j].index > -1) {
+          stack.push(points[j].index);
+          i++;
+        }
+      }
+      sp = stack.length;
+      for (;j < plen; ++j) {
+        if (points[j].index < 0) continue;
+        while (!d3_geom_hullCCW(stack[sp - 2], stack[sp - 1], points[j].index, vertices)) {
+          --sp;
+        }
+        stack[sp++] = points[j].index;
+      }
+      var poly = [];
+      for (i = sp - 1; i >= 0; --i) poly.push(data[stack[i]]);
+      return poly;
+    }
+    hull.x = function(_) {
+      return arguments.length ? (x = _, hull) : x;
+    };
+    hull.y = function(_) {
+      return arguments.length ? (y = _, hull) : y;
+    };
+    return hull;
+  };
+  function d3_geom_hullCCW(i1, i2, i3, v) {
+    var t, a, b, c, d, e, f;
+    t = v[i1];
+    a = t[0];
+    b = t[1];
+    t = v[i2];
+    c = t[0];
+    d = t[1];
+    t = v[i3];
+    e = t[0];
+    f = t[1];
+    return (f - b) * (c - a) - (d - b) * (e - a) > 0;
+  }
+  d3.geom.polygon = function(coordinates) {
+    d3_subclass(coordinates, d3_geom_polygonPrototype);
+    return coordinates;
+  };
+  var d3_geom_polygonPrototype = d3.geom.polygon.prototype = [];
+  d3_geom_polygonPrototype.area = function() {
+    var i = -1, n = this.length, a, b = this[n - 1], area = 0;
+    while (++i < n) {
+      a = b;
+      b = this[i];
+      area += a[1] * b[0] - a[0] * b[1];
+    }
+    return area * .5;
+  };
+  d3_geom_polygonPrototype.centroid = function(k) {
+    var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c;
+    if (!arguments.length) k = -1 / (6 * this.area());
+    while (++i < n) {
+      a = b;
+      b = this[i];
+      c = a[0] * b[1] - b[0] * a[1];
+      x += (a[0] + b[0]) * c;
+      y += (a[1] + b[1]) * c;
+    }
+    return [ x * k, y * k ];
+  };
+  d3_geom_polygonPrototype.clip = function(subject) {
+    var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d;
+    while (++i < n) {
+      input = subject.slice();
+      subject.length = 0;
+      b = this[i];
+      c = input[(m = input.length - closed) - 1];
+      j = -1;
+      while (++j < m) {
+        d = input[j];
+        if (d3_geom_polygonInside(d, a, b)) {
+          if (!d3_geom_polygonInside(c, a, b)) {
+            subject.push(d3_geom_polygonIntersect(c, d, a, b));
+          }
+          subject.push(d);
+        } else if (d3_geom_polygonInside(c, a, b)) {
+          subject.push(d3_geom_polygonIntersect(c, d, a, b));
+        }
+        c = d;
+      }
+      if (closed) subject.push(subject[0]);
+      a = b;
+    }
+    return subject;
+  };
+  function d3_geom_polygonInside(p, a, b) {
+    return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
+  }
+  function d3_geom_polygonIntersect(c, d, a, b) {
+    var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
+    return [ x1 + ua * x21, y1 + ua * y21 ];
+  }
+  function d3_geom_polygonClosed(coordinates) {
+    var a = coordinates[0], b = coordinates[coordinates.length - 1];
+    return !(a[0] - b[0] || a[1] - b[1]);
+  }
+  d3.geom.delaunay = function(vertices) {
+    var edges = vertices.map(function() {
+      return [];
+    }), triangles = [];
+    d3_geom_voronoiTessellate(vertices, function(e) {
+      edges[e.region.l.index].push(vertices[e.region.r.index]);
+    });
+    edges.forEach(function(edge, i) {
+      var v = vertices[i], cx = v[0], cy = v[1];
+      edge.forEach(function(v) {
+        v.angle = Math.atan2(v[0] - cx, v[1] - cy);
+      });
+      edge.sort(function(a, b) {
+        return a.angle - b.angle;
+      });
+      for (var j = 0, m = edge.length - 1; j < m; j++) {
+        triangles.push([ v, edge[j], edge[j + 1] ]);
+      }
+    });
+    return triangles;
+  };
+  d3.geom.voronoi = function(points) {
+    var x = d3_svg_lineX, y = d3_svg_lineY, clipPolygon = null;
+    if (arguments.length) return voronoi(points);
+    function voronoi(data) {
+      var points, polygons = data.map(function() {
+        return [];
+      }), fx = d3_functor(x), fy = d3_functor(y), d, i, n = data.length, Z = 1e6;
+      if (fx === d3_svg_lineX && fy === d3_svg_lineY) points = data; else for (points = new Array(n), 
+      i = 0; i < n; ++i) {
+        points[i] = [ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ];
+      }
+      d3_geom_voronoiTessellate(points, function(e) {
+        var s1, s2, x1, x2, y1, y2;
+        if (e.a === 1 && e.b >= 0) {
+          s1 = e.ep.r;
+          s2 = e.ep.l;
+        } else {
+          s1 = e.ep.l;
+          s2 = e.ep.r;
+        }
+        if (e.a === 1) {
+          y1 = s1 ? s1.y : -Z;
+          x1 = e.c - e.b * y1;
+          y2 = s2 ? s2.y : Z;
+          x2 = e.c - e.b * y2;
+        } else {
+          x1 = s1 ? s1.x : -Z;
+          y1 = e.c - e.a * x1;
+          x2 = s2 ? s2.x : Z;
+          y2 = e.c - e.a * x2;
+        }
+        var v1 = [ x1, y1 ], v2 = [ x2, y2 ];
+        polygons[e.region.l.index].push(v1, v2);
+        polygons[e.region.r.index].push(v1, v2);
+      });
+      polygons = polygons.map(function(polygon, i) {
+        var cx = points[i][0], cy = points[i][1], angle = polygon.map(function(v) {
+          return Math.atan2(v[0] - cx, v[1] - cy);
+        }), order = d3.range(polygon.length).sort(function(a, b) {
+          return angle[a] - angle[b];
+        });
+        return order.filter(function(d, i) {
+          return !i || angle[d] - angle[order[i - 1]] > Îµ;
+        }).map(function(d) {
+          return polygon[d];
+        });
+      });
+      polygons.forEach(function(polygon, i) {
+        var n = polygon.length;
+        if (!n) return polygon.push([ -Z, -Z ], [ -Z, Z ], [ Z, Z ], [ Z, -Z ]);
+        if (n > 2) return;
+        var p0 = points[i], p1 = polygon[0], p2 = polygon[1], x0 = p0[0], y0 = p0[1], x1 = p1[0], y1 = p1[1], x2 = p2[0], y2 = p2[1], dx = Math.abs(x2 - x1), dy = y2 - y1;
+        if (Math.abs(dy) < Îµ) {
+          var y = y0 < y1 ? -Z : Z;
+          polygon.push([ -Z, y ], [ Z, y ]);
+        } else if (dx < Îµ) {
+          var x = x0 < x1 ? -Z : Z;
+          polygon.push([ x, -Z ], [ x, Z ]);
+        } else {
+          var y = (x2 - x1) * (y1 - y0) < (x1 - x0) * (y2 - y1) ? Z : -Z, z = Math.abs(dy) - dx;
+          if (Math.abs(z) < Îµ) {
+            polygon.push([ dy < 0 ? y : -y, y ]);
+          } else {
+            if (z > 0) y *= -1;
+            polygon.push([ -Z, y ], [ Z, y ]);
+          }
+        }
+      });
+      if (clipPolygon) for (i = 0; i < n; ++i) clipPolygon.clip(polygons[i]);
+      for (i = 0; i < n; ++i) polygons[i].point = data[i];
+      return polygons;
+    }
+    voronoi.x = function(_) {
+      return arguments.length ? (x = _, voronoi) : x;
+    };
+    voronoi.y = function(_) {
+      return arguments.length ? (y = _, voronoi) : y;
+    };
+    voronoi.clipExtent = function(_) {
+      if (!arguments.length) return clipPolygon && [ clipPolygon[0], clipPolygon[2] ];
+      if (_ == null) clipPolygon = null; else {
+        var x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], y2 = +_[1][1];
+        clipPolygon = d3.geom.polygon([ [ x1, y1 ], [ x1, y2 ], [ x2, y2 ], [ x2, y1 ] ]);
+      }
+      return voronoi;
+    };
+    voronoi.size = function(_) {
+      if (!arguments.length) return clipPolygon && clipPolygon[2];
+      return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]);
+    };
+    voronoi.links = function(data) {
+      var points, graph = data.map(function() {
+        return [];
+      }), links = [], fx = d3_functor(x), fy = d3_functor(y), d, i, n = data.length;
+      if (fx === d3_svg_lineX && fy === d3_svg_lineY) points = data; else for (points = new Array(n), 
+      i = 0; i < n; ++i) {
+        points[i] = [ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ];
+      }
+      d3_geom_voronoiTessellate(points, function(e) {
+        var l = e.region.l.index, r = e.region.r.index;
+        if (graph[l][r]) return;
+        graph[l][r] = graph[r][l] = true;
+        links.push({
+          source: data[l],
+          target: data[r]
+        });
+      });
+      return links;
+    };
+    voronoi.triangles = function(data) {
+      if (x === d3_svg_lineX && y === d3_svg_lineY) return d3.geom.delaunay(data);
+      var points = new Array(n), fx = d3_functor(x), fy = d3_functor(y), d, i = -1, n = data.length;
+      while (++i < n) {
+        (points[i] = [ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ]).data = d;
+      }
+      return d3.geom.delaunay(points).map(function(triangle) {
+        return triangle.map(function(point) {
+          return point.data;
+        });
+      });
+    };
+    return voronoi;
+  };
+  var d3_geom_voronoiOpposite = {
+    l: "r",
+    r: "l"
+  };
+  function d3_geom_voronoiTessellate(points, callback) {
+    var Sites = {
+      list: points.map(function(v, i) {
+        return {
+          index: i,
+          x: v[0],
+          y: v[1]
+        };
+      }).sort(function(a, b) {
+        return a.y < b.y ? -1 : a.y > b.y ? 1 : a.x < b.x ? -1 : a.x > b.x ? 1 : 0;
+      }),
+      bottomSite: null
+    };
+    var EdgeList = {
+      list: [],
+      leftEnd: null,
+      rightEnd: null,
+      init: function() {
+        EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l");
+        EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l");
+        EdgeList.leftEnd.r = EdgeList.rightEnd;
+        EdgeList.rightEnd.l = EdgeList.leftEnd;
+        EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd);
+      },
+      createHalfEdge: function(edge, side) {
+        return {
+          edge: edge,
+          side: side,
+          vertex: null,
+          l: null,
+          r: null
+        };
+      },
+      insert: function(lb, he) {
+        he.l = lb;
+        he.r = lb.r;
+        lb.r.l = he;
+        lb.r = he;
+      },
+      leftBound: function(p) {
+        var he = EdgeList.leftEnd;
+        do {
+          he = he.r;
+        } while (he != EdgeList.rightEnd && Geom.rightOf(he, p));
+        he = he.l;
+        return he;
+      },
+      del: function(he) {
+        he.l.r = he.r;
+        he.r.l = he.l;
+        he.edge = null;
+      },
+      right: function(he) {
+        return he.r;
+      },
+      left: function(he) {
+        return he.l;
+      },
+      leftRegion: function(he) {
+        return he.edge == null ? Sites.bottomSite : he.edge.region[he.side];
+      },
+      rightRegion: function(he) {
+        return he.edge == null ? Sites.bottomSite : he.edge.region[d3_geom_voronoiOpposite[he.side]];
+      }
+    };
+    var Geom = {
+      bisect: function(s1, s2) {
+        var newEdge = {
+          region: {
+            l: s1,
+            r: s2
+          },
+          ep: {
+            l: null,
+            r: null
+          }
+        };
+        var dx = s2.x - s1.x, dy = s2.y - s1.y, adx = dx > 0 ? dx : -dx, ady = dy > 0 ? dy : -dy;
+        newEdge.c = s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * .5;
+        if (adx > ady) {
+          newEdge.a = 1;
+          newEdge.b = dy / dx;
+          newEdge.c /= dx;
+        } else {
+          newEdge.b = 1;
+          newEdge.a = dx / dy;
+          newEdge.c /= dy;
+        }
+        return newEdge;
+      },
+      intersect: function(el1, el2) {
+        var e1 = el1.edge, e2 = el2.edge;
+        if (!e1 || !e2 || e1.region.r == e2.region.r) {
+          return null;
+        }
+        var d = e1.a * e2.b - e1.b * e2.a;
+        if (Math.abs(d) < 1e-10) {
+          return null;
+        }
+        var xint = (e1.c * e2.b - e2.c * e1.b) / d, yint = (e2.c * e1.a - e1.c * e2.a) / d, e1r = e1.region.r, e2r = e2.region.r, el, e;
+        if (e1r.y < e2r.y || e1r.y == e2r.y && e1r.x < e2r.x) {
+          el = el1;
+          e = e1;
+        } else {
+          el = el2;
+          e = e2;
+        }
+        var rightOfSite = xint >= e.region.r.x;
+        if (rightOfSite && el.side === "l" || !rightOfSite && el.side === "r") {
+          return null;
+        }
+        return {
+          x: xint,
+          y: yint
+        };
+      },
+      rightOf: function(he, p) {
+        var e = he.edge, topsite = e.region.r, rightOfSite = p.x > topsite.x;
+        if (rightOfSite && he.side === "l") {
+          return 1;
+        }
+        if (!rightOfSite && he.side === "r") {
+          return 0;
+        }
+        if (e.a === 1) {
+          var dyp = p.y - topsite.y, dxp = p.x - topsite.x, fast = 0, above = 0;
+          if (!rightOfSite && e.b < 0 || rightOfSite && e.b >= 0) {
+            above = fast = dyp >= e.b * dxp;
+          } else {
+            above = p.x + p.y * e.b > e.c;
+            if (e.b < 0) {
+              above = !above;
+            }
+            if (!above) {
+              fast = 1;
+            }
+          }
+          if (!fast) {
+            var dxs = topsite.x - e.region.l.x;
+            above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b);
+            if (e.b < 0) {
+              above = !above;
+            }
+          }
+        } else {
+          var yl = e.c - e.a * p.x, t1 = p.y - yl, t2 = p.x - topsite.x, t3 = yl - topsite.y;
+          above = t1 * t1 > t2 * t2 + t3 * t3;
+        }
+        return he.side === "l" ? above : !above;
+      },
+      endPoint: function(edge, side, site) {
+        edge.ep[side] = site;
+        if (!edge.ep[d3_geom_voronoiOpposite[side]]) return;
+        callback(edge);
+      },
+      distance: function(s, t) {
+        var dx = s.x - t.x, dy = s.y - t.y;
+        return Math.sqrt(dx * dx + dy * dy);
+      }
+    };
+    var EventQueue = {
+      list: [],
+      insert: function(he, site, offset) {
+        he.vertex = site;
+        he.ystar = site.y + offset;
+        for (var i = 0, list = EventQueue.list, l = list.length; i < l; i++) {
+          var next = list[i];
+          if (he.ystar > next.ystar || he.ystar == next.ystar && site.x > next.vertex.x) {
+            continue;
+          } else {
+            break;
+          }
+        }
+        list.splice(i, 0, he);
+      },
+      del: function(he) {
+        for (var i = 0, ls = EventQueue.list, l = ls.length; i < l && ls[i] != he; ++i) {}
+        ls.splice(i, 1);
+      },
+      empty: function() {
+        return EventQueue.list.length === 0;
+      },
+      nextEvent: function(he) {
+        for (var i = 0, ls = EventQueue.list, l = ls.length; i < l; ++i) {
+          if (ls[i] == he) return ls[i + 1];
+        }
+        return null;
+      },
+      min: function() {
+        var elem = EventQueue.list[0];
+        return {
+          x: elem.vertex.x,
+          y: elem.ystar
+        };
+      },
+      extractMin: function() {
+        return EventQueue.list.shift();
+      }
+    };
+    EdgeList.init();
+    Sites.bottomSite = Sites.list.shift();
+    var newSite = Sites.list.shift(), newIntStar;
+    var lbnd, rbnd, llbnd, rrbnd, bisector;
+    var bot, top, temp, p, v;
+    var e, pm;
+    while (true) {
+      if (!EventQueue.empty()) {
+        newIntStar = EventQueue.min();
+      }
+      if (newSite && (EventQueue.empty() || newSite.y < newIntStar.y || newSite.y == newIntStar.y && newSite.x < newIntStar.x)) {
+        lbnd = EdgeList.leftBound(newSite);
+        rbnd = EdgeList.right(lbnd);
+        bot = EdgeList.rightRegion(lbnd);
+        e = Geom.bisect(bot, newSite);
+        bisector = EdgeList.createHalfEdge(e, "l");
+        EdgeList.insert(lbnd, bisector);
+        p = Geom.intersect(lbnd, bisector);
+        if (p) {
+          EventQueue.del(lbnd);
+          EventQueue.insert(lbnd, p, Geom.distance(p, newSite));
+        }
+        lbnd = bisector;
+        bisector = EdgeList.createHalfEdge(e, "r");
+        EdgeList.insert(lbnd, bisector);
+        p = Geom.intersect(bisector, rbnd);
+        if (p) {
+          EventQueue.insert(bisector, p, Geom.distance(p, newSite));
+        }
+        newSite = Sites.list.shift();
+      } else if (!EventQueue.empty()) {
+        lbnd = EventQueue.extractMin();
+        llbnd = EdgeList.left(lbnd);
+        rbnd = EdgeList.right(lbnd);
+        rrbnd = EdgeList.right(rbnd);
+        bot = EdgeList.leftRegion(lbnd);
+        top = EdgeList.rightRegion(rbnd);
+        v = lbnd.vertex;
+        Geom.endPoint(lbnd.edge, lbnd.side, v);
+        Geom.endPoint(rbnd.edge, rbnd.side, v);
+        EdgeList.del(lbnd);
+        EventQueue.del(rbnd);
+        EdgeList.del(rbnd);
+        pm = "l";
+        if (bot.y > top.y) {
+          temp = bot;
+          bot = top;
+          top = temp;
+          pm = "r";
+        }
+        e = Geom.bisect(bot, top);
+        bisector = EdgeList.createHalfEdge(e, pm);
+        EdgeList.insert(llbnd, bisector);
+        Geom.endPoint(e, d3_geom_voronoiOpposite[pm], v);
+        p = Geom.intersect(llbnd, bisector);
+        if (p) {
+          EventQueue.del(llbnd);
+          EventQueue.insert(llbnd, p, Geom.distance(p, bot));
+        }
+        p = Geom.intersect(bisector, rrbnd);
+        if (p) {
+          EventQueue.insert(bisector, p, Geom.distance(p, bot));
+        }
+      } else {
+        break;
+      }
+    }
+    for (lbnd = EdgeList.right(EdgeList.leftEnd); lbnd != EdgeList.rightEnd; lbnd = EdgeList.right(lbnd)) {
+      callback(lbnd.edge);
+    }
+  }
+  d3.geom.quadtree = function(points, x1, y1, x2, y2) {
+    var x = d3_svg_lineX, y = d3_svg_lineY, compat;
+    if (compat = arguments.length) {
+      x = d3_geom_quadtreeCompatX;
+      y = d3_geom_quadtreeCompatY;
+      if (compat === 3) {
+        y2 = y1;
+        x2 = x1;
+        y1 = x1 = 0;
+      }
+      return quadtree(points);
+    }
+    function quadtree(data) {
+      var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_;
+      if (x1 != null) {
+        x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2;
+      } else {
+        x2_ = y2_ = -(x1_ = y1_ = Infinity);
+        xs = [], ys = [];
+        n = data.length;
+        if (compat) for (i = 0; i < n; ++i) {
+          d = data[i];
+          if (d.x < x1_) x1_ = d.x;
+          if (d.y < y1_) y1_ = d.y;
+          if (d.x > x2_) x2_ = d.x;
+          if (d.y > y2_) y2_ = d.y;
+          xs.push(d.x);
+          ys.push(d.y);
+        } else for (i = 0; i < n; ++i) {
+          var x_ = +fx(d = data[i], i), y_ = +fy(d, i);
+          if (x_ < x1_) x1_ = x_;
+          if (y_ < y1_) y1_ = y_;
+          if (x_ > x2_) x2_ = x_;
+          if (y_ > y2_) y2_ = y_;
+          xs.push(x_);
+          ys.push(y_);
+        }
+      }
+      var dx = x2_ - x1_, dy = y2_ - y1_;
+      if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy;
+      function insert(n, d, x, y, x1, y1, x2, y2) {
+        if (isNaN(x) || isNaN(y)) return;
+        if (n.leaf) {
+          var nx = n.x, ny = n.y;
+          if (nx != null) {
+            if (Math.abs(nx - x) + Math.abs(ny - y) < .01) {
+              insertChild(n, d, x, y, x1, y1, x2, y2);
+            } else {
+              var nPoint = n.point;
+              n.x = n.y = n.point = null;
+              insertChild(n, nPoint, nx, ny, x1, y1, x2, y2);
+              insertChild(n, d, x, y, x1, y1, x2, y2);
+            }
+          } else {
+            n.x = x, n.y = y, n.point = d;
+          }
+        } else {
+          insertChild(n, d, x, y, x1, y1, x2, y2);
+        }
+      }
+      function insertChild(n, d, x, y, x1, y1, x2, y2) {
+        var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, right = x >= sx, bottom = y >= sy, i = (bottom << 1) + right;
+        n.leaf = false;
+        n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode());
+        if (right) x1 = sx; else x2 = sx;
+        if (bottom) y1 = sy; else y2 = sy;
+        insert(n, d, x, y, x1, y1, x2, y2);
+      }
+      var root = d3_geom_quadtreeNode();
+      root.add = function(d) {
+        insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_);
+      };
+      root.visit = function(f) {
+        d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_);
+      };
+      i = -1;
+      if (x1 == null) {
+        while (++i < n) {
+          insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_);
+        }
+        --i;
+      } else data.forEach(root.add);
+      xs = ys = data = d = null;
+      return root;
+    }
+    quadtree.x = function(_) {
+      return arguments.length ? (x = _, quadtree) : x;
+    };
+    quadtree.y = function(_) {
+      return arguments.length ? (y = _, quadtree) : y;
+    };
+    quadtree.extent = function(_) {
+      if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ];
+      if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], 
+      y2 = +_[1][1];
+      return quadtree;
+    };
+    quadtree.size = function(_) {
+      if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ];
+      if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1];
+      return quadtree;
+    };
+    return quadtree;
+  };
+  function d3_geom_quadtreeCompatX(d) {
+    return d.x;
+  }
+  function d3_geom_quadtreeCompatY(d) {
+    return d.y;
+  }
+  function d3_geom_quadtreeNode() {
+    return {
+      leaf: true,
+      nodes: [],
+      point: null,
+      x: null,
+      y: null
+    };
+  }
+  function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) {
+    if (!f(node, x1, y1, x2, y2)) {
+      var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes;
+      if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy);
+      if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy);
+      if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2);
+      if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2);
+    }
+  }
+  d3.interpolateRgb = d3_interpolateRgb;
+  function d3_interpolateRgb(a, b) {
+    a = d3.rgb(a);
+    b = d3.rgb(b);
+    var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab;
+    return function(t) {
+      return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t));
+    };
+  }
+  d3.interpolateObject = d3_interpolateObject;
+  function d3_interpolateObject(a, b) {
+    var i = {}, c = {}, k;
+    for (k in a) {
+      if (k in b) {
+        i[k] = d3_interpolate(a[k], b[k]);
+      } else {
+        c[k] = a[k];
+      }
+    }
+    for (k in b) {
+      if (!(k in a)) {
+        c[k] = b[k];
+      }
+    }
+    return function(t) {
+      for (k in i) c[k] = i[k](t);
+      return c;
+    };
+  }
+  d3.interpolateNumber = d3_interpolateNumber;
+  function d3_interpolateNumber(a, b) {
+    b -= a = +a;
+    return function(t) {
+      return a + b * t;
+    };
+  }
+  d3.interpolateString = d3_interpolateString;
+  function d3_interpolateString(a, b) {
+    var m, i, j, s0 = 0, s1 = 0, s = [], q = [], n, o;
+    a = a + "", b = b + "";
+    d3_interpolate_number.lastIndex = 0;
+    for (i = 0; m = d3_interpolate_number.exec(b); ++i) {
+      if (m.index) s.push(b.substring(s0, s1 = m.index));
+      q.push({
+        i: s.length,
+        x: m[0]
+      });
+      s.push(null);
+      s0 = d3_interpolate_number.lastIndex;
+    }
+    if (s0 < b.length) s.push(b.substring(s0));
+    for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) {
+      o = q[i];
+      if (o.x == m[0]) {
+        if (o.i) {
+          if (s[o.i + 1] == null) {
+            s[o.i - 1] += o.x;
+            s.splice(o.i, 1);
+            for (j = i + 1; j < n; ++j) q[j].i--;
+          } else {
+            s[o.i - 1] += o.x + s[o.i + 1];
+            s.splice(o.i, 2);
+            for (j = i + 1; j < n; ++j) q[j].i -= 2;
+          }
+        } else {
+          if (s[o.i + 1] == null) {
+            s[o.i] = o.x;
+          } else {
+            s[o.i] = o.x + s[o.i + 1];
+            s.splice(o.i + 1, 1);
+            for (j = i + 1; j < n; ++j) q[j].i--;
+          }
+        }
+        q.splice(i, 1);
+        n--;
+        i--;
+      } else {
+        o.x = d3_interpolateNumber(parseFloat(m[0]), parseFloat(o.x));
+      }
+    }
+    while (i < n) {
+      o = q.pop();
+      if (s[o.i + 1] == null) {
+        s[o.i] = o.x;
+      } else {
+        s[o.i] = o.x + s[o.i + 1];
+        s.splice(o.i + 1, 1);
+      }
+      n--;
+    }
+    if (s.length === 1) {
+      return s[0] == null ? (o = q[0].x, function(t) {
+        return o(t) + "";
+      }) : function() {
+        return b;
+      };
+    }
+    return function(t) {
+      for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t);
+      return s.join("");
+    };
+  }
+  var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g;
+  d3.interpolate = d3_interpolate;
+  function d3_interpolate(a, b) {
+    var i = d3.interpolators.length, f;
+    while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ;
+    return f;
+  }
+  d3.interpolators = [ function(a, b) {
+    var t = typeof b;
+    return (t === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_Color ? d3_interpolateRgb : t === "object" ? Array.isArray(b) ? d3_interpolateArray : d3_interpolateObject : d3_interpolateNumber)(a, b);
+  } ];
+  d3.interpolateArray = d3_interpolateArray;
+  function d3_interpolateArray(a, b) {
+    var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i;
+    for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i]));
+    for (;i < na; ++i) c[i] = a[i];
+    for (;i < nb; ++i) c[i] = b[i];
+    return function(t) {
+      for (i = 0; i < n0; ++i) c[i] = x[i](t);
+      return c;
+    };
+  }
+  var d3_ease_default = function() {
+    return d3_identity;
+  };
+  var d3_ease = d3.map({
+    linear: d3_ease_default,
+    poly: d3_ease_poly,
+    quad: function() {
+      return d3_ease_quad;
+    },
+    cubic: function() {
+      return d3_ease_cubic;
+    },
+    sin: function() {
+      return d3_ease_sin;
+    },
+    exp: function() {
+      return d3_ease_exp;
+    },
+    circle: function() {
+      return d3_ease_circle;
+    },
+    elastic: d3_ease_elastic,
+    back: d3_ease_back,
+    bounce: function() {
+      return d3_ease_bounce;
+    }
+  });
+  var d3_ease_mode = d3.map({
+    "in": d3_identity,
+    out: d3_ease_reverse,
+    "in-out": d3_ease_reflect,
+    "out-in": function(f) {
+      return d3_ease_reflect(d3_ease_reverse(f));
+    }
+  });
+  d3.ease = function(name) {
+    var i = name.indexOf("-"), t = i >= 0 ? name.substring(0, i) : name, m = i >= 0 ? name.substring(i + 1) : "in";
+    t = d3_ease.get(t) || d3_ease_default;
+    m = d3_ease_mode.get(m) || d3_identity;
+    return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1))));
+  };
+  function d3_ease_clamp(f) {
+    return function(t) {
+      return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
+    };
+  }
+  function d3_ease_reverse(f) {
+    return function(t) {
+      return 1 - f(1 - t);
+    };
+  }
+  function d3_ease_reflect(f) {
+    return function(t) {
+      return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t));
+    };
+  }
+  function d3_ease_quad(t) {
+    return t * t;
+  }
+  function d3_ease_cubic(t) {
+    return t * t * t;
+  }
+  function d3_ease_cubicInOut(t) {
+    if (t <= 0) return 0;
+    if (t >= 1) return 1;
+    var t2 = t * t, t3 = t2 * t;
+    return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
+  }
+  function d3_ease_poly(e) {
+    return function(t) {
+      return Math.pow(t, e);
+    };
+  }
+  function d3_ease_sin(t) {
+    return 1 - Math.cos(t * Ï / 2);
+  }
+  function d3_ease_exp(t) {
+    return Math.pow(2, 10 * (t - 1));
+  }
+  function d3_ease_circle(t) {
+    return 1 - Math.sqrt(1 - t * t);
+  }
+  function d3_ease_elastic(a, p) {
+    var s;
+    if (arguments.length < 2) p = .45;
+    if (arguments.length) s = p / (2 * Ï) * Math.asin(1 / a); else a = 1, s = p / 4;
+    return function(t) {
+      return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * Ï / p);
+    };
+  }
+  function d3_ease_back(s) {
+    if (!s) s = 1.70158;
+    return function(t) {
+      return t * t * ((s + 1) * t - s);
+    };
+  }
+  function d3_ease_bounce(t) {
+    return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
+  }
+  d3.interpolateHcl = d3_interpolateHcl;
+  function d3_interpolateHcl(a, b) {
+    a = d3.hcl(a);
+    b = d3.hcl(b);
+    var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al;
+    if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac;
+    if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
+    return function(t) {
+      return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + "";
+    };
+  }
+  d3.interpolateHsl = d3_interpolateHsl;
+  function d3_interpolateHsl(a, b) {
+    a = d3.hsl(a);
+    b = d3.hsl(b);
+    var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al;
+    if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as;
+    if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
+    return function(t) {
+      return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + "";
+    };
+  }
+  d3.interpolateLab = d3_interpolateLab;
+  function d3_interpolateLab(a, b) {
+    a = d3.lab(a);
+    b = d3.lab(b);
+    var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab;
+    return function(t) {
+      return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + "";
+    };
+  }
+  d3.interpolateRound = d3_interpolateRound;
+  function d3_interpolateRound(a, b) {
+    b -= a;
+    return function(t) {
+      return Math.round(a + b * t);
+    };
+  }
+  d3.transform = function(string) {
+    var g = d3_document.createElementNS(d3.ns.prefix.svg, "g");
+    return (d3.transform = function(string) {
+      if (string != null) {
+        g.setAttribute("transform", string);
+        var t = g.transform.baseVal.consolidate();
+      }
+      return new d3_transform(t ? t.matrix : d3_transformIdentity);
+    })(string);
+  };
+  function d3_transform(m) {
+    var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0;
+    if (r0[0] * r1[1] < r1[0] * r0[1]) {
+      r0[0] *= -1;
+      r0[1] *= -1;
+      kx *= -1;
+      kz *= -1;
+    }
+    this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees;
+    this.translate = [ m.e, m.f ];
+    this.scale = [ kx, ky ];
+    this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0;
+  }
+  d3_transform.prototype.toString = function() {
+    return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")";
+  };
+  function d3_transformDot(a, b) {
+    return a[0] * b[0] + a[1] * b[1];
+  }
+  function d3_transformNormalize(a) {
+    var k = Math.sqrt(d3_transformDot(a, a));
+    if (k) {
+      a[0] /= k;
+      a[1] /= k;
+    }
+    return k;
+  }
+  function d3_transformCombine(a, b, k) {
+    a[0] += k * b[0];
+    a[1] += k * b[1];
+    return a;
+  }
+  var d3_transformIdentity = {
+    a: 1,
+    b: 0,
+    c: 0,
+    d: 1,
+    e: 0,
+    f: 0
+  };
+  d3.interpolateTransform = d3_interpolateTransform;
+  function d3_interpolateTransform(a, b) {
+    var s = [], q = [], n, A = d3.transform(a), B = d3.transform(b), ta = A.translate, tb = B.translate, ra = A.rotate, rb = B.rotate, wa = A.skew, wb = B.skew, ka = A.scale, kb = B.scale;
+    if (ta[0] != tb[0] || ta[1] != tb[1]) {
+      s.push("translate(", null, ",", null, ")");
+      q.push({
+        i: 1,
+        x: d3_interpolateNumber(ta[0], tb[0])
+      }, {
+        i: 3,
+        x: d3_interpolateNumber(ta[1], tb[1])
+      });
+    } else if (tb[0] || tb[1]) {
+      s.push("translate(" + tb + ")");
+    } else {
+      s.push("");
+    }
+    if (ra != rb) {
+      if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360;
+      q.push({
+        i: s.push(s.pop() + "rotate(", null, ")") - 2,
+        x: d3_interpolateNumber(ra, rb)
+      });
+    } else if (rb) {
+      s.push(s.pop() + "rotate(" + rb + ")");
+    }
+    if (wa != wb) {
+      q.push({
+        i: s.push(s.pop() + "skewX(", null, ")") - 2,
+        x: d3_interpolateNumber(wa, wb)
+      });
+    } else if (wb) {
+      s.push(s.pop() + "skewX(" + wb + ")");
+    }
+    if (ka[0] != kb[0] || ka[1] != kb[1]) {
+      n = s.push(s.pop() + "scale(", null, ",", null, ")");
+      q.push({
+        i: n - 4,
+        x: d3_interpolateNumber(ka[0], kb[0])
+      }, {
+        i: n - 2,
+        x: d3_interpolateNumber(ka[1], kb[1])
+      });
+    } else if (kb[0] != 1 || kb[1] != 1) {
+      s.push(s.pop() + "scale(" + kb + ")");
+    }
+    n = q.length;
+    return function(t) {
+      var i = -1, o;
+      while (++i < n) s[(o = q[i]).i] = o.x(t);
+      return s.join("");
+    };
+  }
+  function d3_uninterpolateNumber(a, b) {
+    b = b - (a = +a) ? 1 / (b - a) : 0;
+    return function(x) {
+      return (x - a) * b;
+    };
+  }
+  function d3_uninterpolateClamp(a, b) {
+    b = b - (a = +a) ? 1 / (b - a) : 0;
+    return function(x) {
+      return Math.max(0, Math.min(1, (x - a) * b));
+    };
+  }
+  d3.layout = {};
+  d3.layout.bundle = function() {
+    return function(links) {
+      var paths = [], i = -1, n = links.length;
+      while (++i < n) paths.push(d3_layout_bundlePath(links[i]));
+      return paths;
+    };
+  };
+  function d3_layout_bundlePath(link) {
+    var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ];
+    while (start !== lca) {
+      start = start.parent;
+      points.push(start);
+    }
+    var k = points.length;
+    while (end !== lca) {
+      points.splice(k, 0, end);
+      end = end.parent;
+    }
+    return points;
+  }
+  function d3_layout_bundleAncestors(node) {
+    var ancestors = [], parent = node.parent;
+    while (parent != null) {
+      ancestors.push(node);
+      node = parent;
+      parent = parent.parent;
+    }
+    ancestors.push(node);
+    return ancestors;
+  }
+  function d3_layout_bundleLeastCommonAncestor(a, b) {
+    if (a === b) return a;
+    var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null;
+    while (aNode === bNode) {
+      sharedNode = aNode;
+      aNode = aNodes.pop();
+      bNode = bNodes.pop();
+    }
+    return sharedNode;
+  }
+  d3.layout.chord = function() {
+    var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
+    function relayout() {
+      var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
+      chords = [];
+      groups = [];
+      k = 0, i = -1;
+      while (++i < n) {
+        x = 0, j = -1;
+        while (++j < n) {
+          x += matrix[i][j];
+        }
+        groupSums.push(x);
+        subgroupIndex.push(d3.range(n));
+        k += x;
+      }
+      if (sortGroups) {
+        groupIndex.sort(function(a, b) {
+          return sortGroups(groupSums[a], groupSums[b]);
+        });
+      }
+      if (sortSubgroups) {
+        subgroupIndex.forEach(function(d, i) {
+          d.sort(function(a, b) {
+            return sortSubgroups(matrix[i][a], matrix[i][b]);
+          });
+        });
+      }
+      k = (2 * Ï - padding * n) / k;
+      x = 0, i = -1;
+      while (++i < n) {
+        x0 = x, j = -1;
+        while (++j < n) {
+          var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
+          subgroups[di + "-" + dj] = {
+            index: di,
+            subindex: dj,
+            startAngle: a0,
+            endAngle: a1,
+            value: v
+          };
+        }
+        groups[di] = {
+          index: di,
+          startAngle: x0,
+          endAngle: x,
+          value: (x - x0) / k
+        };
+        x += padding;
+      }
+      i = -1;
+      while (++i < n) {
+        j = i - 1;
+        while (++j < n) {
+          var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
+          if (source.value || target.value) {
+            chords.push(source.value < target.value ? {
+              source: target,
+              target: source
+            } : {
+              source: source,
+              target: target
+            });
+          }
+        }
+      }
+      if (sortChords) resort();
+    }
+    function resort() {
+      chords.sort(function(a, b) {
+        return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
+      });
+    }
+    chord.matrix = function(x) {
+      if (!arguments.length) return matrix;
+      n = (matrix = x) && matrix.length;
+      chords = groups = null;
+      return chord;
+    };
+    chord.padding = function(x) {
+      if (!arguments.length) return padding;
+      padding = x;
+      chords = groups = null;
+      return chord;
+    };
+    chord.sortGroups = function(x) {
+      if (!arguments.length) return sortGroups;
+      sortGroups = x;
+      chords = groups = null;
+      return chord;
+    };
+    chord.sortSubgroups = function(x) {
+      if (!arguments.length) return sortSubgroups;
+      sortSubgroups = x;
+      chords = null;
+      return chord;
+    };
+    chord.sortChords = function(x) {
+      if (!arguments.length) return sortChords;
+      sortChords = x;
+      if (chords) resort();
+      return chord;
+    };
+    chord.chords = function() {
+      if (!chords) relayout();
+      return chords;
+    };
+    chord.groups = function() {
+      if (!groups) relayout();
+      return groups;
+    };
+    return chord;
+  };
+  d3.layout.force = function() {
+    var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, gravity = .1, theta = .8, nodes = [], links = [], distances, strengths, charges;
+    function repulse(node) {
+      return function(quad, x1, _, x2) {
+        if (quad.point !== node) {
+          var dx = quad.cx - node.x, dy = quad.cy - node.y, dn = 1 / Math.sqrt(dx * dx + dy * dy);
+          if ((x2 - x1) * dn < theta) {
+            var k = quad.charge * dn * dn;
+            node.px -= dx * k;
+            node.py -= dy * k;
+            return true;
+          }
+          if (quad.point && isFinite(dn)) {
+            var k = quad.pointCharge * dn * dn;
+            node.px -= dx * k;
+            node.py -= dy * k;
+          }
+        }
+        return !quad.charge;
+      };
+    }
+    force.tick = function() {
+      if ((alpha *= .99) < .005) {
+        event.end({
+          type: "end",
+          alpha: alpha = 0
+        });
+        return true;
+      }
+      var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y;
+      for (i = 0; i < m; ++i) {
+        o = links[i];
+        s = o.source;
+        t = o.target;
+        x = t.x - s.x;
+        y = t.y - s.y;
+        if (l = x * x + y * y) {
+          l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
+          x *= l;
+          y *= l;
+          t.x -= x * (k = s.weight / (t.weight + s.weight));
+          t.y -= y * k;
+          s.x += x * (k = 1 - k);
+          s.y += y * k;
+        }
+      }
+      if (k = alpha * gravity) {
+        x = size[0] / 2;
+        y = size[1] / 2;
+        i = -1;
+        if (k) while (++i < n) {
+          o = nodes[i];
+          o.x += (x - o.x) * k;
+          o.y += (y - o.y) * k;
+        }
+      }
+      if (charge) {
+        d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
+        i = -1;
+        while (++i < n) {
+          if (!(o = nodes[i]).fixed) {
+            q.visit(repulse(o));
+          }
+        }
+      }
+      i = -1;
+      while (++i < n) {
+        o = nodes[i];
+        if (o.fixed) {
+          o.x = o.px;
+          o.y = o.py;
+        } else {
+          o.x -= (o.px - (o.px = o.x)) * friction;
+          o.y -= (o.py - (o.py = o.y)) * friction;
+        }
+      }
+      event.tick({
+        type: "tick",
+        alpha: alpha
+      });
+    };
+    force.nodes = function(x) {
+      if (!arguments.length) return nodes;
+      nodes = x;
+      return force;
+    };
+    force.links = function(x) {
+      if (!arguments.length) return links;
+      links = x;
+      return force;
+    };
+    force.size = function(x) {
+      if (!arguments.length) return size;
+      size = x;
+      return force;
+    };
+    force.linkDistance = function(x) {
+      if (!arguments.length) return linkDistance;
+      linkDistance = typeof x === "function" ? x : +x;
+      return force;
+    };
+    force.distance = force.linkDistance;
+    force.linkStrength = function(x) {
+      if (!arguments.length) return linkStrength;
+      linkStrength = typeof x === "function" ? x : +x;
+      return force;
+    };
+    force.friction = function(x) {
+      if (!arguments.length) return friction;
+      friction = +x;
+      return force;
+    };
+    force.charge = function(x) {
+      if (!arguments.length) return charge;
+      charge = typeof x === "function" ? x : +x;
+      return force;
+    };
+    force.gravity = function(x) {
+      if (!arguments.length) return gravity;
+      gravity = +x;
+      return force;
+    };
+    force.theta = function(x) {
+      if (!arguments.length) return theta;
+      theta = +x;
+      return force;
+    };
+    force.alpha = function(x) {
+      if (!arguments.length) return alpha;
+      x = +x;
+      if (alpha) {
+        if (x > 0) alpha = x; else alpha = 0;
+      } else if (x > 0) {
+        event.start({
+          type: "start",
+          alpha: alpha = x
+        });
+        d3.timer(force.tick);
+      }
+      return force;
+    };
+    force.start = function() {
+      var i, j, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o;
+      for (i = 0; i < n; ++i) {
+        (o = nodes[i]).index = i;
+        o.weight = 0;
+      }
+      for (i = 0; i < m; ++i) {
+        o = links[i];
+        if (typeof o.source == "number") o.source = nodes[o.source];
+        if (typeof o.target == "number") o.target = nodes[o.target];
+        ++o.source.weight;
+        ++o.target.weight;
+      }
+      for (i = 0; i < n; ++i) {
+        o = nodes[i];
+        if (isNaN(o.x)) o.x = position("x", w);
+        if (isNaN(o.y)) o.y = position("y", h);
+        if (isNaN(o.px)) o.px = o.x;
+        if (isNaN(o.py)) o.py = o.y;
+      }
+      distances = [];
+      if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance;
+      strengths = [];
+      if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength;
+      charges = [];
+      if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge;
+      function position(dimension, size) {
+        var neighbors = neighbor(i), j = -1, m = neighbors.length, x;
+        while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x;
+        return Math.random() * size;
+      }
+      function neighbor() {
+        if (!neighbors) {
+          neighbors = [];
+          for (j = 0; j < n; ++j) {
+            neighbors[j] = [];
+          }
+          for (j = 0; j < m; ++j) {
+            var o = links[j];
+            neighbors[o.source.index].push(o.target);
+            neighbors[o.target.index].push(o.source);
+          }
+        }
+        return neighbors[i];
+      }
+      return force.resume();
+    };
+    force.resume = function() {
+      return force.alpha(.1);
+    };
+    force.stop = function() {
+      return force.alpha(0);
+    };
+    force.drag = function() {
+      if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend);
+      if (!arguments.length) return drag;
+      this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag);
+    };
+    function dragmove(d) {
+      d.px = d3.event.x, d.py = d3.event.y;
+      force.resume();
+    }
+    return d3.rebind(force, event, "on");
+  };
+  function d3_layout_forceDragstart(d) {
+    d.fixed |= 2;
+  }
+  function d3_layout_forceDragend(d) {
+    d.fixed &= ~6;
+  }
+  function d3_layout_forceMouseover(d) {
+    d.fixed |= 4;
+    d.px = d.x, d.py = d.y;
+  }
+  function d3_layout_forceMouseout(d) {
+    d.fixed &= ~4;
+  }
+  function d3_layout_forceAccumulate(quad, alpha, charges) {
+    var cx = 0, cy = 0;
+    quad.charge = 0;
+    if (!quad.leaf) {
+      var nodes = quad.nodes, n = nodes.length, i = -1, c;
+      while (++i < n) {
+        c = nodes[i];
+        if (c == null) continue;
+        d3_layout_forceAccumulate(c, alpha, charges);
+        quad.charge += c.charge;
+        cx += c.charge * c.cx;
+        cy += c.charge * c.cy;
+      }
+    }
+    if (quad.point) {
+      if (!quad.leaf) {
+        quad.point.x += Math.random() - .5;
+        quad.point.y += Math.random() - .5;
+      }
+      var k = alpha * charges[quad.point.index];
+      quad.charge += quad.pointCharge = k;
+      cx += k * quad.point.x;
+      cy += k * quad.point.y;
+    }
+    quad.cx = cx / quad.charge;
+    quad.cy = cy / quad.charge;
+  }
+  var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1;
+  d3.layout.hierarchy = function() {
+    var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue;
+    function recurse(node, depth, nodes) {
+      var childs = children.call(hierarchy, node, depth);
+      node.depth = depth;
+      nodes.push(node);
+      if (childs && (n = childs.length)) {
+        var i = -1, n, c = node.children = [], v = 0, j = depth + 1, d;
+        while (++i < n) {
+          d = recurse(childs[i], j, nodes);
+          d.parent = node;
+          c.push(d);
+          v += d.value;
+        }
+        if (sort) c.sort(sort);
+        if (value) node.value = v;
+      } else if (value) {
+        node.value = +value.call(hierarchy, node, depth) || 0;
+      }
+      return node;
+    }
+    function revalue(node, depth) {
+      var children = node.children, v = 0;
+      if (children && (n = children.length)) {
+        var i = -1, n, j = depth + 1;
+        while (++i < n) v += revalue(children[i], j);
+      } else if (value) {
+        v = +value.call(hierarchy, node, depth) || 0;
+      }
+      if (value) node.value = v;
+      return v;
+    }
+    function hierarchy(d) {
+      var nodes = [];
+      recurse(d, 0, nodes);
+      return nodes;
+    }
+    hierarchy.sort = function(x) {
+      if (!arguments.length) return sort;
+      sort = x;
+      return hierarchy;
+    };
+    hierarchy.children = function(x) {
+      if (!arguments.length) return children;
+      children = x;
+      return hierarchy;
+    };
+    hierarchy.value = function(x) {
+      if (!arguments.length) return value;
+      value = x;
+      return hierarchy;
+    };
+    hierarchy.revalue = function(root) {
+      revalue(root, 0);
+      return root;
+    };
+    return hierarchy;
+  };
+  function d3_layout_hierarchyRebind(object, hierarchy) {
+    d3.rebind(object, hierarchy, "sort", "children", "value");
+    object.nodes = object;
+    object.links = d3_layout_hierarchyLinks;
+    return object;
+  }
+  function d3_layout_hierarchyChildren(d) {
+    return d.children;
+  }
+  function d3_layout_hierarchyValue(d) {
+    return d.value;
+  }
+  function d3_layout_hierarchySort(a, b) {
+    return b.value - a.value;
+  }
+  function d3_layout_hierarchyLinks(nodes) {
+    return d3.merge(nodes.map(function(parent) {
+      return (parent.children || []).map(function(child) {
+        return {
+          source: parent,
+          target: child
+        };
+      });
+    }));
+  }
+  d3.layout.partition = function() {
+    var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ];
+    function position(node, x, dx, dy) {
+      var children = node.children;
+      node.x = x;
+      node.y = node.depth * dy;
+      node.dx = dx;
+      node.dy = dy;
+      if (children && (n = children.length)) {
+        var i = -1, n, c, d;
+        dx = node.value ? dx / node.value : 0;
+        while (++i < n) {
+          position(c = children[i], x, d = c.value * dx, dy);
+          x += d;
+        }
+      }
+    }
+    function depth(node) {
+      var children = node.children, d = 0;
+      if (children && (n = children.length)) {
+        var i = -1, n;
+        while (++i < n) d = Math.max(d, depth(children[i]));
+      }
+      return 1 + d;
+    }
+    function partition(d, i) {
+      var nodes = hierarchy.call(this, d, i);
+      position(nodes[0], 0, size[0], size[1] / depth(nodes[0]));
+      return nodes;
+    }
+    partition.size = function(x) {
+      if (!arguments.length) return size;
+      size = x;
+      return partition;
+    };
+    return d3_layout_hierarchyRebind(partition, hierarchy);
+  };
+  d3.layout.pie = function() {
+    var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = 2 * Ï;
+    function pie(data) {
+      var values = data.map(function(d, i) {
+        return +value.call(pie, d, i);
+      });
+      var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle);
+      var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a) / d3.sum(values);
+      var index = d3.range(data.length);
+      if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) {
+        return values[j] - values[i];
+      } : function(i, j) {
+        return sort(data[i], data[j]);
+      });
+      var arcs = [];
+      index.forEach(function(i) {
+        var d;
+        arcs[i] = {
+          data: data[i],
+          value: d = values[i],
+          startAngle: a,
+          endAngle: a += d * k
+        };
+      });
+      return arcs;
+    }
+    pie.value = function(x) {
+      if (!arguments.length) return value;
+      value = x;
+      return pie;
+    };
+    pie.sort = function(x) {
+      if (!arguments.length) return sort;
+      sort = x;
+      return pie;
+    };
+    pie.startAngle = function(x) {
+      if (!arguments.length) return startAngle;
+      startAngle = x;
+      return pie;
+    };
+    pie.endAngle = function(x) {
+      if (!arguments.length) return endAngle;
+      endAngle = x;
+      return pie;
+    };
+    return pie;
+  };
+  var d3_layout_pieSortByValue = {};
+  d3.layout.stack = function() {
+    var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY;
+    function stack(data, index) {
+      var series = data.map(function(d, i) {
+        return values.call(stack, d, i);
+      });
+      var points = series.map(function(d) {
+        return d.map(function(v, i) {
+          return [ x.call(stack, v, i), y.call(stack, v, i) ];
+        });
+      });
+      var orders = order.call(stack, points, index);
+      series = d3.permute(series, orders);
+      points = d3.permute(points, orders);
+      var offsets = offset.call(stack, points, index);
+      var n = series.length, m = series[0].length, i, j, o;
+      for (j = 0; j < m; ++j) {
+        out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
+        for (i = 1; i < n; ++i) {
+          out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
+        }
+      }
+      return data;
+    }
+    stack.values = function(x) {
+      if (!arguments.length) return values;
+      values = x;
+      return stack;
+    };
+    stack.order = function(x) {
+      if (!arguments.length) return order;
+      order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault;
+      return stack;
+    };
+    stack.offset = function(x) {
+      if (!arguments.length) return offset;
+      offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero;
+      return stack;
+    };
+    stack.x = function(z) {
+      if (!arguments.length) return x;
+      x = z;
+      return stack;
+    };
+    stack.y = function(z) {
+      if (!arguments.length) return y;
+      y = z;
+      return stack;
+    };
+    stack.out = function(z) {
+      if (!arguments.length) return out;
+      out = z;
+      return stack;
+    };
+    return stack;
+  };
+  function d3_layout_stackX(d) {
+    return d.x;
+  }
+  function d3_layout_stackY(d) {
+    return d.y;
+  }
+  function d3_layout_stackOut(d, y0, y) {
+    d.y0 = y0;
+    d.y = y;
+  }
+  var d3_layout_stackOrders = d3.map({
+    "inside-out": function(data) {
+      var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) {
+        return max[a] - max[b];
+      }), top = 0, bottom = 0, tops = [], bottoms = [];
+      for (i = 0; i < n; ++i) {
+        j = index[i];
+        if (top < bottom) {
+          top += sums[j];
+          tops.push(j);
+        } else {
+          bottom += sums[j];
+          bottoms.push(j);
+        }
+      }
+      return bottoms.reverse().concat(tops);
+    },
+    reverse: function(data) {
+      return d3.range(data.length).reverse();
+    },
+    "default": d3_layout_stackOrderDefault
+  });
+  var d3_layout_stackOffsets = d3.map({
+    silhouette: function(data) {
+      var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = [];
+      for (j = 0; j < m; ++j) {
+        for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
+        if (o > max) max = o;
+        sums.push(o);
+      }
+      for (j = 0; j < m; ++j) {
+        y0[j] = (max - sums[j]) / 2;
+      }
+      return y0;
+    },
+    wiggle: function(data) {
+      var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = [];
+      y0[0] = o = o0 = 0;
+      for (j = 1; j < m; ++j) {
+        for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1];
+        for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) {
+          for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) {
+            s3 += (data[k][j][1] - data[k][j - 1][1]) / dx;
+          }
+          s2 += s3 * data[i][j][1];
+        }
+        y0[j] = o -= s1 ? s2 / s1 * dx : 0;
+        if (o < o0) o0 = o;
+      }
+      for (j = 0; j < m; ++j) y0[j] -= o0;
+      return y0;
+    },
+    expand: function(data) {
+      var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = [];
+      for (j = 0; j < m; ++j) {
+        for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
+        if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k;
+      }
+      for (j = 0; j < m; ++j) y0[j] = 0;
+      return y0;
+    },
+    zero: d3_layout_stackOffsetZero
+  });
+  function d3_layout_stackOrderDefault(data) {
+    return d3.range(data.length);
+  }
+  function d3_layout_stackOffsetZero(data) {
+    var j = -1, m = data[0].length, y0 = [];
+    while (++j < m) y0[j] = 0;
+    return y0;
+  }
+  function d3_layout_stackMaxIndex(array) {
+    var i = 1, j = 0, v = array[0][1], k, n = array.length;
+    for (;i < n; ++i) {
+      if ((k = array[i][1]) > v) {
+        j = i;
+        v = k;
+      }
+    }
+    return j;
+  }
+  function d3_layout_stackReduceSum(d) {
+    return d.reduce(d3_layout_stackSum, 0);
+  }
+  function d3_layout_stackSum(p, d) {
+    return p + d[1];
+  }
+  d3.layout.histogram = function() {
+    var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges;
+    function histogram(data, i) {
+      var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x;
+      while (++i < m) {
+        bin = bins[i] = [];
+        bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
+        bin.y = 0;
+      }
+      if (m > 0) {
+        i = -1;
+        while (++i < n) {
+          x = values[i];
+          if (x >= range[0] && x <= range[1]) {
+            bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
+            bin.y += k;
+            bin.push(data[i]);
+          }
+        }
+      }
+      return bins;
+    }
+    histogram.value = function(x) {
+      if (!arguments.length) return valuer;
+      valuer = x;
+      return histogram;
+    };
+    histogram.range = function(x) {
+      if (!arguments.length) return ranger;
+      ranger = d3_functor(x);
+      return histogram;
+    };
+    histogram.bins = function(x) {
+      if (!arguments.length) return binner;
+      binner = typeof x === "number" ? function(range) {
+        return d3_layout_histogramBinFixed(range, x);
+      } : d3_functor(x);
+      return histogram;
+    };
+    histogram.frequency = function(x) {
+      if (!arguments.length) return frequency;
+      frequency = !!x;
+      return histogram;
+    };
+    return histogram;
+  };
+  function d3_layout_histogramBinSturges(range, values) {
+    return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1));
+  }
+  function d3_layout_histogramBinFixed(range, n) {
+    var x = -1, b = +range[0], m = (range[1] - b) / n, f = [];
+    while (++x <= n) f[x] = m * x + b;
+    return f;
+  }
+  function d3_layout_histogramRange(values) {
+    return [ d3.min(values), d3.max(values) ];
+  }
+  d3.layout.tree = function() {
+    var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false;
+    function tree(d, i) {
+      var nodes = hierarchy.call(this, d, i), root = nodes[0];
+      function firstWalk(node, previousSibling) {
+        var children = node.children, layout = node._tree;
+        if (children && (n = children.length)) {
+          var n, firstChild = children[0], previousChild, ancestor = firstChild, child, i = -1;
+          while (++i < n) {
+            child = children[i];
+            firstWalk(child, previousChild);
+            ancestor = apportion(child, previousChild, ancestor);
+            previousChild = child;
+          }
+          d3_layout_treeShift(node);
+          var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim);
+          if (previousSibling) {
+            layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling);
+            layout.mod = layout.prelim - midpoint;
+          } else {
+            layout.prelim = midpoint;
+          }
+        } else {
+          if (previousSibling) {
+            layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling);
+          }
+        }
+      }
+      function secondWalk(node, x) {
+        node.x = node._tree.prelim + x;
+        var children = node.children;
+        if (children && (n = children.length)) {
+          var i = -1, n;
+          x += node._tree.mod;
+          while (++i < n) {
+            secondWalk(children[i], x);
+          }
+        }
+      }
+      function apportion(node, previousSibling, ancestor) {
+        if (previousSibling) {
+          var vip = node, vop = node, vim = previousSibling, vom = node.parent.children[0], sip = vip._tree.mod, sop = vop._tree.mod, sim = vim._tree.mod, som = vom._tree.mod, shift;
+          while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) {
+            vom = d3_layout_treeLeft(vom);
+            vop = d3_layout_treeRight(vop);
+            vop._tree.ancestor = node;
+            shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip);
+            if (shift > 0) {
+              d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift);
+              sip += shift;
+              sop += shift;
+            }
+            sim += vim._tree.mod;
+            sip += vip._tree.mod;
+            som += vom._tree.mod;
+            sop += vop._tree.mod;
+          }
+          if (vim && !d3_layout_treeRight(vop)) {
+            vop._tree.thread = vim;
+            vop._tree.mod += sim - sop;
+          }
+          if (vip && !d3_layout_treeLeft(vom)) {
+            vom._tree.thread = vip;
+            vom._tree.mod += sip - som;
+            ancestor = node;
+          }
+        }
+        return ancestor;
+      }
+      d3_layout_treeVisitAfter(root, function(node, previousSibling) {
+        node._tree = {
+          ancestor: node,
+          prelim: 0,
+          mod: 0,
+          change: 0,
+          shift: 0,
+          number: previousSibling ? previousSibling._tree.number + 1 : 0
+        };
+      });
+      firstWalk(root);
+      secondWalk(root, -root._tree.prelim);
+      var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), right = d3_layout_treeSearch(root, d3_layout_treeRightmost), deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2, y1 = deep.depth || 1;
+      d3_layout_treeVisitAfter(root, nodeSize ? function(node) {
+        node.x *= size[0];
+        node.y = node.depth * size[1];
+        delete node._tree;
+      } : function(node) {
+        node.x = (node.x - x0) / (x1 - x0) * size[0];
+        node.y = node.depth / y1 * size[1];
+        delete node._tree;
+      });
+      return nodes;
+    }
+    tree.separation = function(x) {
+      if (!arguments.length) return separation;
+      separation = x;
+      return tree;
+    };
+    tree.size = function(x) {
+      if (!arguments.length) return nodeSize ? null : size;
+      nodeSize = (size = x) == null;
+      return tree;
+    };
+    tree.nodeSize = function(x) {
+      if (!arguments.length) return nodeSize ? size : null;
+      nodeSize = (size = x) != null;
+      return tree;
+    };
+    return d3_layout_hierarchyRebind(tree, hierarchy);
+  };
+  function d3_layout_treeSeparation(a, b) {
+    return a.parent == b.parent ? 1 : 2;
+  }
+  function d3_layout_treeLeft(node) {
+    var children = node.children;
+    return children && children.length ? children[0] : node._tree.thread;
+  }
+  function d3_layout_treeRight(node) {
+    var children = node.children, n;
+    return children && (n = children.length) ? children[n - 1] : node._tree.thread;
+  }
+  function d3_layout_treeSearch(node, compare) {
+    var children = node.children;
+    if (children && (n = children.length)) {
+      var child, n, i = -1;
+      while (++i < n) {
+        if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) {
+          node = child;
+        }
+      }
+    }
+    return node;
+  }
+  function d3_layout_treeRightmost(a, b) {
+    return a.x - b.x;
+  }
+  function d3_layout_treeLeftmost(a, b) {
+    return b.x - a.x;
+  }
+  function d3_layout_treeDeepest(a, b) {
+    return a.depth - b.depth;
+  }
+  function d3_layout_treeVisitAfter(node, callback) {
+    function visit(node, previousSibling) {
+      var children = node.children;
+      if (children && (n = children.length)) {
+        var child, previousChild = null, i = -1, n;
+        while (++i < n) {
+          child = children[i];
+          visit(child, previousChild);
+          previousChild = child;
+        }
+      }
+      callback(node, previousSibling);
+    }
+    visit(node, null);
+  }
+  function d3_layout_treeShift(node) {
+    var shift = 0, change = 0, children = node.children, i = children.length, child;
+    while (--i >= 0) {
+      child = children[i]._tree;
+      child.prelim += shift;
+      child.mod += shift;
+      shift += child.shift + (change += child.change);
+    }
+  }
+  function d3_layout_treeMove(ancestor, node, shift) {
+    ancestor = ancestor._tree;
+    node = node._tree;
+    var change = shift / (node.number - ancestor.number);
+    ancestor.change += change;
+    node.change -= change;
+    node.shift += shift;
+    node.prelim += shift;
+    node.mod += shift;
+  }
+  function d3_layout_treeAncestor(vim, node, ancestor) {
+    return vim._tree.ancestor.parent == node.parent ? vim._tree.ancestor : ancestor;
+  }
+  d3.layout.pack = function() {
+    var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius;
+    function pack(d, i) {
+      var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() {
+        return radius;
+      };
+      root.x = root.y = 0;
+      d3_layout_treeVisitAfter(root, function(d) {
+        d.r = +r(d.value);
+      });
+      d3_layout_treeVisitAfter(root, d3_layout_packSiblings);
+      if (padding) {
+        var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2;
+        d3_layout_treeVisitAfter(root, function(d) {
+          d.r += dr;
+        });
+        d3_layout_treeVisitAfter(root, d3_layout_packSiblings);
+        d3_layout_treeVisitAfter(root, function(d) {
+          d.r -= dr;
+        });
+      }
+      d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h));
+      return nodes;
+    }
+    pack.size = function(_) {
+      if (!arguments.length) return size;
+      size = _;
+      return pack;
+    };
+    pack.radius = function(_) {
+      if (!arguments.length) return radius;
+      radius = _ == null || typeof _ === "function" ? _ : +_;
+      return pack;
+    };
+    pack.padding = function(_) {
+      if (!arguments.length) return padding;
+      padding = +_;
+      return pack;
+    };
+    return d3_layout_hierarchyRebind(pack, hierarchy);
+  };
+  function d3_layout_packSort(a, b) {
+    return a.value - b.value;
+  }
+  function d3_layout_packInsert(a, b) {
+    var c = a._pack_next;
+    a._pack_next = b;
+    b._pack_prev = a;
+    b._pack_next = c;
+    c._pack_prev = b;
+  }
+  function d3_layout_packSplice(a, b) {
+    a._pack_next = b;
+    b._pack_prev = a;
+  }
+  function d3_layout_packIntersects(a, b) {
+    var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r;
+    return .999 * dr * dr > dx * dx + dy * dy;
+  }
+  function d3_layout_packSiblings(node) {
+    if (!(nodes = node.children) || !(n = nodes.length)) return;
+    var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n;
+    function bound(node) {
+      xMin = Math.min(node.x - node.r, xMin);
+      xMax = Math.max(node.x + node.r, xMax);
+      yMin = Math.min(node.y - node.r, yMin);
+      yMax = Math.max(node.y + node.r, yMax);
+    }
+    nodes.forEach(d3_layout_packLink);
+    a = nodes[0];
+    a.x = -a.r;
+    a.y = 0;
+    bound(a);
+    if (n > 1) {
+      b = nodes[1];
+      b.x = b.r;
+      b.y = 0;
+      bound(b);
+      if (n > 2) {
+        c = nodes[2];
+        d3_layout_packPlace(a, b, c);
+        bound(c);
+        d3_layout_packInsert(a, c);
+        a._pack_prev = c;
+        d3_layout_packInsert(c, b);
+        b = a._pack_next;
+        for (i = 3; i < n; i++) {
+          d3_layout_packPlace(a, b, c = nodes[i]);
+          var isect = 0, s1 = 1, s2 = 1;
+          for (j = b._pack_next; j !== b; j = j._pack_next, s1++) {
+            if (d3_layout_packIntersects(j, c)) {
+              isect = 1;
+              break;
+            }
+          }
+          if (isect == 1) {
+            for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) {
+              if (d3_layout_packIntersects(k, c)) {
+                break;
+              }
+            }
+          }
+          if (isect) {
+            if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b);
+            i--;
+          } else {
+            d3_layout_packInsert(a, c);
+            b = c;
+            bound(c);
+          }
+        }
+      }
+    }
+    var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0;
+    for (i = 0; i < n; i++) {
+      c = nodes[i];
+      c.x -= cx;
+      c.y -= cy;
+      cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y));
+    }
+    node.r = cr;
+    nodes.forEach(d3_layout_packUnlink);
+  }
+  function d3_layout_packLink(node) {
+    node._pack_next = node._pack_prev = node;
+  }
+  function d3_layout_packUnlink(node) {
+    delete node._pack_next;
+    delete node._pack_prev;
+  }
+  function d3_layout_packTransform(node, x, y, k) {
+    var children = node.children;
+    node.x = x += k * node.x;
+    node.y = y += k * node.y;
+    node.r *= k;
+    if (children) {
+      var i = -1, n = children.length;
+      while (++i < n) d3_layout_packTransform(children[i], x, y, k);
+    }
+  }
+  function d3_layout_packPlace(a, b, c) {
+    var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y;
+    if (db && (dx || dy)) {
+      var da = b.r + c.r, dc = dx * dx + dy * dy;
+      da *= da;
+      db *= db;
+      var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
+      c.x = a.x + x * dx + y * dy;
+      c.y = a.y + x * dy - y * dx;
+    } else {
+      c.x = a.x + db;
+      c.y = a.y;
+    }
+  }
+  d3.layout.cluster = function() {
+    var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false;
+    function cluster(d, i) {
+      var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0;
+      d3_layout_treeVisitAfter(root, function(node) {
+        var children = node.children;
+        if (children && children.length) {
+          node.x = d3_layout_clusterX(children);
+          node.y = d3_layout_clusterY(children);
+        } else {
+          node.x = previousNode ? x += separation(node, previousNode) : 0;
+          node.y = 0;
+          previousNode = node;
+        }
+      });
+      var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2;
+      d3_layout_treeVisitAfter(root, nodeSize ? function(node) {
+        node.x = (node.x - root.x) * size[0];
+        node.y = (root.y - node.y) * size[1];
+      } : function(node) {
+        node.x = (node.x - x0) / (x1 - x0) * size[0];
+        node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1];
+      });
+      return nodes;
+    }
+    cluster.separation = function(x) {
+      if (!arguments.length) return separation;
+      separation = x;
+      return cluster;
+    };
+    cluster.size = function(x) {
+      if (!arguments.length) return nodeSize ? null : size;
+      nodeSize = (size = x) == null;
+      return cluster;
+    };
+    cluster.nodeSize = function(x) {
+      if (!arguments.length) return nodeSize ? size : null;
+      nodeSize = (size = x) != null;
+      return cluster;
+    };
+    return d3_layout_hierarchyRebind(cluster, hierarchy);
+  };
+  function d3_layout_clusterY(children) {
+    return 1 + d3.max(children, function(child) {
+      return child.y;
+    });
+  }
+  function d3_layout_clusterX(children) {
+    return children.reduce(function(x, child) {
+      return x + child.x;
+    }, 0) / children.length;
+  }
+  function d3_layout_clusterLeft(node) {
+    var children = node.children;
+    return children && children.length ? d3_layout_clusterLeft(children[0]) : node;
+  }
+  function d3_layout_clusterRight(node) {
+    var children = node.children, n;
+    return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node;
+  }
+  d3.layout.treemap = function() {
+    var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5));
+    function scale(children, k) {
+      var i = -1, n = children.length, child, area;
+      while (++i < n) {
+        area = (child = children[i]).value * (k < 0 ? 0 : k);
+        child.area = isNaN(area) || area <= 0 ? 0 : area;
+      }
+    }
+    function squarify(node) {
+      var children = node.children;
+      if (children && children.length) {
+        var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n;
+        scale(remaining, rect.dx * rect.dy / node.value);
+        row.area = 0;
+        while ((n = remaining.length) > 0) {
+          row.push(child = remaining[n - 1]);
+          row.area += child.area;
+          if (mode !== "squarify" || (score = worst(row, u)) <= best) {
+            remaining.pop();
+            best = score;
+          } else {
+            row.area -= row.pop().area;
+            position(row, u, rect, false);
+            u = Math.min(rect.dx, rect.dy);
+            row.length = row.area = 0;
+            best = Infinity;
+          }
+        }
+        if (row.length) {
+          position(row, u, rect, true);
+          row.length = row.area = 0;
+        }
+        children.forEach(squarify);
+      }
+    }
+    function stickify(node) {
+      var children = node.children;
+      if (children && children.length) {
+        var rect = pad(node), remaining = children.slice(), child, row = [];
+        scale(remaining, rect.dx * rect.dy / node.value);
+        row.area = 0;
+        while (child = remaining.pop()) {
+          row.push(child);
+          row.area += child.area;
+          if (child.z != null) {
+            position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length);
+            row.length = row.area = 0;
+          }
+        }
+        children.forEach(stickify);
+      }
+    }
+    function worst(row, u) {
+      var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length;
+      while (++i < n) {
+        if (!(r = row[i].area)) continue;
+        if (r < rmin) rmin = r;
+        if (r > rmax) rmax = r;
+      }
+      s *= s;
+      u *= u;
+      return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity;
+    }
+    function position(row, u, rect, flush) {
+      var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o;
+      if (u == rect.dx) {
+        if (flush || v > rect.dy) v = rect.dy;
+        while (++i < n) {
+          o = row[i];
+          o.x = x;
+          o.y = y;
+          o.dy = v;
+          x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0);
+        }
+        o.z = true;
+        o.dx += rect.x + rect.dx - x;
+        rect.y += v;
+        rect.dy -= v;
+      } else {
+        if (flush || v > rect.dx) v = rect.dx;
+        while (++i < n) {
+          o = row[i];
+          o.x = x;
+          o.y = y;
+          o.dx = v;
+          y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0);
+        }
+        o.z = false;
+        o.dy += rect.y + rect.dy - y;
+        rect.x += v;
+        rect.dx -= v;
+      }
+    }
+    function treemap(d) {
+      var nodes = stickies || hierarchy(d), root = nodes[0];
+      root.x = 0;
+      root.y = 0;
+      root.dx = size[0];
+      root.dy = size[1];
+      if (stickies) hierarchy.revalue(root);
+      scale([ root ], root.dx * root.dy / root.value);
+      (stickies ? stickify : squarify)(root);
+      if (sticky) stickies = nodes;
+      return nodes;
+    }
+    treemap.size = function(x) {
+      if (!arguments.length) return size;
+      size = x;
+      return treemap;
+    };
+    treemap.padding = function(x) {
+      if (!arguments.length) return padding;
+      function padFunction(node) {
+        var p = x.call(treemap, node, node.depth);
+        return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p);
+      }
+      function padConstant(node) {
+        return d3_layout_treemapPad(node, x);
+      }
+      var type;
+      pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], 
+      padConstant) : padConstant;
+      return treemap;
+    };
+    treemap.round = function(x) {
+      if (!arguments.length) return round != Number;
+      round = x ? Math.round : Number;
+      return treemap;
+    };
+    treemap.sticky = function(x) {
+      if (!arguments.length) return sticky;
+      sticky = x;
+      stickies = null;
+      return treemap;
+    };
+    treemap.ratio = function(x) {
+      if (!arguments.length) return ratio;
+      ratio = x;
+      return treemap;
+    };
+    treemap.mode = function(x) {
+      if (!arguments.length) return mode;
+      mode = x + "";
+      return treemap;
+    };
+    return d3_layout_hierarchyRebind(treemap, hierarchy);
+  };
+  function d3_layout_treemapPadNull(node) {
+    return {
+      x: node.x,
+      y: node.y,
+      dx: node.dx,
+      dy: node.dy
+    };
+  }
+  function d3_layout_treemapPad(node, padding) {
+    var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2];
+    if (dx < 0) {
+      x += dx / 2;
+      dx = 0;
+    }
+    if (dy < 0) {
+      y += dy / 2;
+      dy = 0;
+    }
+    return {
+      x: x,
+      y: y,
+      dx: dx,
+      dy: dy
+    };
+  }
+  d3.random = {
+    normal: function(Âµ, Ï) {
+      var n = arguments.length;
+      if (n < 2) Ï = 1;
+      if (n < 1) Âµ = 0;
+      return function() {
+        var x, y, r;
+        do {
+          x = Math.random() * 2 - 1;
+          y = Math.random() * 2 - 1;
+          r = x * x + y * y;
+        } while (!r || r > 1);
+        return Âµ + Ï * x * Math.sqrt(-2 * Math.log(r) / r);
+      };
+    },
+    logNormal: function() {
+      var random = d3.random.normal.apply(d3, arguments);
+      return function() {
+        return Math.exp(random());
+      };
+    },
+    irwinHall: function(m) {
+      return function() {
+        for (var s = 0, j = 0; j < m; j++) s += Math.random();
+        return s / m;
+      };
+    }
+  };
+  d3.scale = {};
+  function d3_scaleExtent(domain) {
+    var start = domain[0], stop = domain[domain.length - 1];
+    return start < stop ? [ start, stop ] : [ stop, start ];
+  }
+  function d3_scaleRange(scale) {
+    return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
+  }
+  function d3_scale_bilinear(domain, range, uninterpolate, interpolate) {
+    var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]);
+    return function(x) {
+      return i(u(x));
+    };
+  }
+  function d3_scale_nice(domain, nice) {
+    var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx;
+    if (x1 < x0) {
+      dx = i0, i0 = i1, i1 = dx;
+      dx = x0, x0 = x1, x1 = dx;
+    }
+    domain[i0] = nice.floor(x0);
+    domain[i1] = nice.ceil(x1);
+    return domain;
+  }
+  function d3_scale_niceStep(step) {
+    return step ? {
+      floor: function(x) {
+        return Math.floor(x / step) * step;
+      },
+      ceil: function(x) {
+        return Math.ceil(x / step) * step;
+      }
+    } : d3_scale_niceIdentity;
+  }
+  var d3_scale_niceIdentity = {
+    floor: d3_identity,
+    ceil: d3_identity
+  };
+  function d3_scale_polylinear(domain, range, uninterpolate, interpolate) {
+    var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1;
+    if (domain[k] < domain[0]) {
+      domain = domain.slice().reverse();
+      range = range.slice().reverse();
+    }
+    while (++j <= k) {
+      u.push(uninterpolate(domain[j - 1], domain[j]));
+      i.push(interpolate(range[j - 1], range[j]));
+    }
+    return function(x) {
+      var j = d3.bisect(domain, x, 1, k) - 1;
+      return i[j](u[j](x));
+    };
+  }
+  d3.scale.linear = function() {
+    return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false);
+  };
+  function d3_scale_linear(domain, range, interpolate, clamp) {
+    var output, input;
+    function rescale() {
+      var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber;
+      output = linear(domain, range, uninterpolate, interpolate);
+      input = linear(range, domain, uninterpolate, d3_interpolate);
+      return scale;
+    }
+    function scale(x) {
+      return output(x);
+    }
+    scale.invert = function(y) {
+      return input(y);
+    };
+    scale.domain = function(x) {
+      if (!arguments.length) return domain;
+      domain = x.map(Number);
+      return rescale();
+    };
+    scale.range = function(x) {
+      if (!arguments.length) return range;
+      range = x;
+      return rescale();
+    };
+    scale.rangeRound = function(x) {
+      return scale.range(x).interpolate(d3_interpolateRound);
+    };
+    scale.clamp = function(x) {
+      if (!arguments.length) return clamp;
+      clamp = x;
+      return rescale();
+    };
+    scale.interpolate = function(x) {
+      if (!arguments.length) return interpolate;
+      interpolate = x;
+      return rescale();
+    };
+    scale.ticks = function(m) {
+      return d3_scale_linearTicks(domain, m);
+    };
+    scale.tickFormat = function(m, format) {
+      return d3_scale_linearTickFormat(domain, m, format);
+    };
+    scale.nice = function(m) {
+      d3_scale_linearNice(domain, m);
+      return rescale();
+    };
+    scale.copy = function() {
+      return d3_scale_linear(domain, range, interpolate, clamp);
+    };
+    return rescale();
+  }
+  function d3_scale_linearRebind(scale, linear) {
+    return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
+  }
+  function d3_scale_linearNice(domain, m) {
+    return d3_scale_nice(domain, d3_scale_niceStep(m ? d3_scale_linearTickRange(domain, m)[2] : d3_scale_linearNiceStep(domain)));
+  }
+  function d3_scale_linearNiceStep(domain) {
+    var extent = d3_scaleExtent(domain), span = extent[1] - extent[0];
+    return Math.pow(10, Math.round(Math.log(span) / Math.LN10) - 1);
+  }
+  function d3_scale_linearTickRange(domain, m) {
+    var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step;
+    if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2;
+    extent[0] = Math.ceil(extent[0] / step) * step;
+    extent[1] = Math.floor(extent[1] / step) * step + step * .5;
+    extent[2] = step;
+    return extent;
+  }
+  function d3_scale_linearTicks(domain, m) {
+    return d3.range.apply(d3, d3_scale_linearTickRange(domain, m));
+  }
+  function d3_scale_linearTickFormat(domain, m, format) {
+    var precision = -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01);
+    return d3.format(format ? format.replace(d3_format_re, function(a, b, c, d, e, f, g, h, i, j) {
+      return [ b, c, d, e, f, g, h, i || "." + (precision - (j === "%") * 2), j ].join("");
+    }) : ",." + precision + "f");
+  }
+  d3.scale.log = function() {
+    return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]);
+  };
+  function d3_scale_log(linear, base, positive, domain) {
+    function log(x) {
+      return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base);
+    }
+    function pow(x) {
+      return positive ? Math.pow(base, x) : -Math.pow(base, -x);
+    }
+    function scale(x) {
+      return linear(log(x));
+    }
+    scale.invert = function(x) {
+      return pow(linear.invert(x));
+    };
+    scale.domain = function(x) {
+      if (!arguments.length) return domain;
+      positive = x[0] >= 0;
+      linear.domain((domain = x.map(Number)).map(log));
+      return scale;
+    };
+    scale.base = function(_) {
+      if (!arguments.length) return base;
+      base = +_;
+      linear.domain(domain.map(log));
+      return scale;
+    };
+    scale.nice = function() {
+      var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative);
+      linear.domain(niced);
+      domain = niced.map(pow);
+      return scale;
+    };
+    scale.ticks = function() {
+      var extent = d3_scaleExtent(domain), ticks = [];
+      if (extent.every(isFinite)) {
+        var u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base;
+        if (positive) {
+          for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k);
+          ticks.push(pow(i));
+        } else {
+          ticks.push(pow(i));
+          for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k);
+        }
+        for (i = 0; ticks[i] < u; i++) {}
+        for (j = ticks.length; ticks[j - 1] > v; j--) {}
+        ticks = ticks.slice(i, j);
+      }
+      return ticks;
+    };
+    scale.tickFormat = function(n, format) {
+      if (!arguments.length) return d3_scale_logFormat;
+      if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format);
+      var k = Math.max(.1, n / scale.ticks().length), f = positive ? (e = 1e-12, Math.ceil) : (e = -1e-12, 
+      Math.floor), e;
+      return function(d) {
+        return d / pow(f(log(d) + e)) <= k ? format(d) : "";
+      };
+    };
+    scale.copy = function() {
+      return d3_scale_log(linear.copy(), base, positive, domain);
+    };
+    return d3_scale_linearRebind(scale, linear);
+  }
+  var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = {
+    floor: function(x) {
+      return -Math.ceil(-x);
+    },
+    ceil: function(x) {
+      return -Math.floor(-x);
+    }
+  };
+  d3.scale.pow = function() {
+    return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]);
+  };
+  function d3_scale_pow(linear, exponent, domain) {
+    var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent);
+    function scale(x) {
+      return linear(powp(x));
+    }
+    scale.invert = function(x) {
+      return powb(linear.invert(x));
+    };
+    scale.domain = function(x) {
+      if (!arguments.length) return domain;
+      linear.domain((domain = x.map(Number)).map(powp));
+      return scale;
+    };
+    scale.ticks = function(m) {
+      return d3_scale_linearTicks(domain, m);
+    };
+    scale.tickFormat = function(m, format) {
+      return d3_scale_linearTickFormat(domain, m, format);
+    };
+    scale.nice = function(m) {
+      return scale.domain(d3_scale_linearNice(domain, m));
+    };
+    scale.exponent = function(x) {
+      if (!arguments.length) return exponent;
+      powp = d3_scale_powPow(exponent = x);
+      powb = d3_scale_powPow(1 / exponent);
+      linear.domain(domain.map(powp));
+      return scale;
+    };
+    scale.copy = function() {
+      return d3_scale_pow(linear.copy(), exponent, domain);
+    };
+    return d3_scale_linearRebind(scale, linear);
+  }
+  function d3_scale_powPow(e) {
+    return function(x) {
+      return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e);
+    };
+  }
+  d3.scale.sqrt = function() {
+    return d3.scale.pow().exponent(.5);
+  };
+  d3.scale.ordinal = function() {
+    return d3_scale_ordinal([], {
+      t: "range",
+      a: [ [] ]
+    });
+  };
+  function d3_scale_ordinal(domain, ranger) {
+    var index, range, rangeBand;
+    function scale(x) {
+      return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length];
+    }
+    function steps(start, step) {
+      return d3.range(domain.length).map(function(i) {
+        return start + step * i;
+      });
+    }
+    scale.domain = function(x) {
+      if (!arguments.length) return domain;
+      domain = [];
+      index = new d3_Map();
+      var i = -1, n = x.length, xi;
+      while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi));
+      return scale[ranger.t].apply(scale, ranger.a);
+    };
+    scale.range = function(x) {
+      if (!arguments.length) return range;
+      range = x;
+      rangeBand = 0;
+      ranger = {
+        t: "range",
+        a: arguments
+      };
+      return scale;
+    };
+    scale.rangePoints = function(x, padding) {
+      if (arguments.length < 2) padding = 0;
+      var start = x[0], stop = x[1], step = (stop - start) / (Math.max(1, domain.length - 1) + padding);
+      range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step);
+      rangeBand = 0;
+      ranger = {
+        t: "rangePoints",
+        a: arguments
+      };
+      return scale;
+    };
+    scale.rangeBands = function(x, padding, outerPadding) {
+      if (arguments.length < 2) padding = 0;
+      if (arguments.length < 3) outerPadding = padding;
+      var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding);
+      range = steps(start + step * outerPadding, step);
+      if (reverse) range.reverse();
+      rangeBand = step * (1 - padding);
+      ranger = {
+        t: "rangeBands",
+        a: arguments
+      };
+      return scale;
+    };
+    scale.rangeRoundBands = function(x, padding, outerPadding) {
+      if (arguments.length < 2) padding = 0;
+      if (arguments.length < 3) outerPadding = padding;
+      var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)), error = stop - start - (domain.length - padding) * step;
+      range = steps(start + Math.round(error / 2), step);
+      if (reverse) range.reverse();
+      rangeBand = Math.round(step * (1 - padding));
+      ranger = {
+        t: "rangeRoundBands",
+        a: arguments
+      };
+      return scale;
+    };
+    scale.rangeBand = function() {
+      return rangeBand;
+    };
+    scale.rangeExtent = function() {
+      return d3_scaleExtent(ranger.a[0]);
+    };
+    scale.copy = function() {
+      return d3_scale_ordinal(domain, ranger);
+    };
+    return scale.domain(domain);
+  }
+  d3.scale.category10 = function() {
+    return d3.scale.ordinal().range(d3_category10);
+  };
+  d3.scale.category20 = function() {
+    return d3.scale.ordinal().range(d3_category20);
+  };
+  d3.scale.category20b = function() {
+    return d3.scale.ordinal().range(d3_category20b);
+  };
+  d3.scale.category20c = function() {
+    return d3.scale.ordinal().range(d3_category20c);
+  };
+  var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString);
+  var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString);
+  var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString);
+  var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString);
+  d3.scale.quantile = function() {
+    return d3_scale_quantile([], []);
+  };
+  function d3_scale_quantile(domain, range) {
+    var thresholds;
+    function rescale() {
+      var k = 0, q = range.length;
+      thresholds = [];
+      while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q);
+      return scale;
+    }
+    function scale(x) {
+      if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)];
+    }
+    scale.domain = function(x) {
+      if (!arguments.length) return domain;
+      domain = x.filter(function(d) {
+        return !isNaN(d);
+      }).sort(d3.ascending);
+      return rescale();
+    };
+    scale.range = function(x) {
+      if (!arguments.length) return range;
+      range = x;
+      return rescale();
+    };
+    scale.quantiles = function() {
+      return thresholds;
+    };
+    scale.invertExtent = function(y) {
+      y = range.indexOf(y);
+      return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ];
+    };
+    scale.copy = function() {
+      return d3_scale_quantile(domain, range);
+    };
+    return rescale();
+  }
+  d3.scale.quantize = function() {
+    return d3_scale_quantize(0, 1, [ 0, 1 ]);
+  };
+  function d3_scale_quantize(x0, x1, range) {
+    var kx, i;
+    function scale(x) {
+      return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))];
+    }
+    function rescale() {
+      kx = range.length / (x1 - x0);
+      i = range.length - 1;
+      return scale;
+    }
+    scale.domain = function(x) {
+      if (!arguments.length) return [ x0, x1 ];
+      x0 = +x[0];
+      x1 = +x[x.length - 1];
+      return rescale();
+    };
+    scale.range = function(x) {
+      if (!arguments.length) return range;
+      range = x;
+      return rescale();
+    };
+    scale.invertExtent = function(y) {
+      y = range.indexOf(y);
+      y = y < 0 ? NaN : y / kx + x0;
+      return [ y, y + 1 / kx ];
+    };
+    scale.copy = function() {
+      return d3_scale_quantize(x0, x1, range);
+    };
+    return rescale();
+  }
+  d3.scale.threshold = function() {
+    return d3_scale_threshold([ .5 ], [ 0, 1 ]);
+  };
+  function d3_scale_threshold(domain, range) {
+    function scale(x) {
+      if (x <= x) return range[d3.bisect(domain, x)];
+    }
+    scale.domain = function(_) {
+      if (!arguments.length) return domain;
+      domain = _;
+      return scale;
+    };
+    scale.range = function(_) {
+      if (!arguments.length) return range;
+      range = _;
+      return scale;
+    };
+    scale.invertExtent = function(y) {
+      y = range.indexOf(y);
+      return [ domain[y - 1], domain[y] ];
+    };
+    scale.copy = function() {
+      return d3_scale_threshold(domain, range);
+    };
+    return scale;
+  }
+  d3.scale.identity = function() {
+    return d3_scale_identity([ 0, 1 ]);
+  };
+  function d3_scale_identity(domain) {
+    function identity(x) {
+      return +x;
+    }
+    identity.invert = identity;
+    identity.domain = identity.range = function(x) {
+      if (!arguments.length) return domain;
+      domain = x.map(identity);
+      return identity;
+    };
+    identity.ticks = function(m) {
+      return d3_scale_linearTicks(domain, m);
+    };
+    identity.tickFormat = function(m, format) {
+      return d3_scale_linearTickFormat(domain, m, format);
+    };
+    identity.copy = function() {
+      return d3_scale_identity(domain);
+    };
+    return identity;
+  }
+  d3.svg.arc = function() {
+    var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
+    function arc() {
+      var r0 = innerRadius.apply(this, arguments), r1 = outerRadius.apply(this, arguments), a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, da = (a1 < a0 && (da = a0, 
+      a0 = a1, a1 = da), a1 - a0), df = da < Ï ? "0" : "1", c0 = Math.cos(a0), s0 = Math.sin(a0), c1 = Math.cos(a1), s1 = Math.sin(a1);
+      return da >= d3_svg_arcMax ? r0 ? "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "M0," + r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + -r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 + "Z" : "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "Z" : r0 ? "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L" + r0 * c1 + "," + r0 * s1 + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 + "Z" : "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L0,0" + "Z";
+    }
+    arc.innerRadius = function(v) {
+      if (!arguments.length) return innerRadius;
+      innerRadius = d3_functor(v);
+      return arc;
+    };
+    arc.outerRadius = function(v) {
+      if (!arguments.length) return outerRadius;
+      outerRadius = d3_functor(v);
+      return arc;
+    };
+    arc.startAngle = function(v) {
+      if (!arguments.length) return startAngle;
+      startAngle = d3_functor(v);
+      return arc;
+    };
+    arc.endAngle = function(v) {
+      if (!arguments.length) return endAngle;
+      endAngle = d3_functor(v);
+      return arc;
+    };
+    arc.centroid = function() {
+      var r = (innerRadius.apply(this, arguments) + outerRadius.apply(this, arguments)) / 2, a = (startAngle.apply(this, arguments) + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset;
+      return [ Math.cos(a) * r, Math.sin(a) * r ];
+    };
+    return arc;
+  };
+  var d3_svg_arcOffset = -Ï / 2, d3_svg_arcMax = 2 * Ï - 1e-6;
+  function d3_svg_arcInnerRadius(d) {
+    return d.innerRadius;
+  }
+  function d3_svg_arcOuterRadius(d) {
+    return d.outerRadius;
+  }
+  function d3_svg_arcStartAngle(d) {
+    return d.startAngle;
+  }
+  function d3_svg_arcEndAngle(d) {
+    return d.endAngle;
+  }
+  d3.svg.line.radial = function() {
+    var line = d3_svg_line(d3_svg_lineRadial);
+    line.radius = line.x, delete line.x;
+    line.angle = line.y, delete line.y;
+    return line;
+  };
+  function d3_svg_lineRadial(points) {
+    var point, i = -1, n = points.length, r, a;
+    while (++i < n) {
+      point = points[i];
+      r = point[0];
+      a = point[1] + d3_svg_arcOffset;
+      point[0] = r * Math.cos(a);
+      point[1] = r * Math.sin(a);
+    }
+    return points;
+  }
+  function d3_svg_area(projection) {
+    var x0 = d3_svg_lineX, x1 = d3_svg_lineX, y0 = 0, y1 = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7;
+    function area(data) {
+      var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() {
+        return x;
+      } : d3_functor(x1), fy1 = y0 === y1 ? function() {
+        return y;
+      } : d3_functor(y1), x, y;
+      function segment() {
+        segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z");
+      }
+      while (++i < n) {
+        if (defined.call(this, d = data[i], i)) {
+          points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]);
+          points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]);
+        } else if (points0.length) {
+          segment();
+          points0 = [];
+          points1 = [];
+        }
+      }
+      if (points0.length) segment();
+      return segments.length ? segments.join("") : null;
+    }
+    area.x = function(_) {
+      if (!arguments.length) return x1;
+      x0 = x1 = _;
+      return area;
+    };
+    area.x0 = function(_) {
+      if (!arguments.length) return x0;
+      x0 = _;
+      return area;
+    };
+    area.x1 = function(_) {
+      if (!arguments.length) return x1;
+      x1 = _;
+      return area;
+    };
+    area.y = function(_) {
+      if (!arguments.length) return y1;
+      y0 = y1 = _;
+      return area;
+    };
+    area.y0 = function(_) {
+      if (!arguments.length) return y0;
+      y0 = _;
+      return area;
+    };
+    area.y1 = function(_) {
+      if (!arguments.length) return y1;
+      y1 = _;
+      return area;
+    };
+    area.defined = function(_) {
+      if (!arguments.length) return defined;
+      defined = _;
+      return area;
+    };
+    area.interpolate = function(_) {
+      if (!arguments.length) return interpolateKey;
+      if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
+      interpolateReverse = interpolate.reverse || interpolate;
+      L = interpolate.closed ? "M" : "L";
+      return area;
+    };
+    area.tension = function(_) {
+      if (!arguments.length) return tension;
+      tension = _;
+      return area;
+    };
+    return area;
+  }
+  d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter;
+  d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore;
+  d3.svg.area = function() {
+    return d3_svg_area(d3_identity);
+  };
+  d3.svg.area.radial = function() {
+    var area = d3_svg_area(d3_svg_lineRadial);
+    area.radius = area.x, delete area.x;
+    area.innerRadius = area.x0, delete area.x0;
+    area.outerRadius = area.x1, delete area.x1;
+    area.angle = area.y, delete area.y;
+    area.startAngle = area.y0, delete area.y0;
+    area.endAngle = area.y1, delete area.y1;
+    return area;
+  };
+  d3.svg.chord = function() {
+    var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
+    function chord(d, i) {
+      var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i);
+      return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
+    }
+    function subgroup(self, f, d, i) {
+      var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset;
+      return {
+        r: r,
+        a0: a0,
+        a1: a1,
+        p0: [ r * Math.cos(a0), r * Math.sin(a0) ],
+        p1: [ r * Math.cos(a1), r * Math.sin(a1) ]
+      };
+    }
+    function equals(a, b) {
+      return a.a0 == b.a0 && a.a1 == b.a1;
+    }
+    function arc(r, p, a) {
+      return "A" + r + "," + r + " 0 " + +(a > Ï) + ",1 " + p;
+    }
+    function curve(r0, p0, r1, p1) {
+      return "Q 0,0 " + p1;
+    }
+    chord.radius = function(v) {
+      if (!arguments.length) return radius;
+      radius = d3_functor(v);
+      return chord;
+    };
+    chord.source = function(v) {
+      if (!arguments.length) return source;
+      source = d3_functor(v);
+      return chord;
+    };
+    chord.target = function(v) {
+      if (!arguments.length) return target;
+      target = d3_functor(v);
+      return chord;
+    };
+    chord.startAngle = function(v) {
+      if (!arguments.length) return startAngle;
+      startAngle = d3_functor(v);
+      return chord;
+    };
+    chord.endAngle = function(v) {
+      if (!arguments.length) return endAngle;
+      endAngle = d3_functor(v);
+      return chord;
+    };
+    return chord;
+  };
+  function d3_svg_chordRadius(d) {
+    return d.radius;
+  }
+  d3.svg.diagonal = function() {
+    var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection;
+    function diagonal(d, i) {
+      var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, {
+        x: p0.x,
+        y: m
+      }, {
+        x: p3.x,
+        y: m
+      }, p3 ];
+      p = p.map(projection);
+      return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
+    }
+    diagonal.source = function(x) {
+      if (!arguments.length) return source;
+      source = d3_functor(x);
+      return diagonal;
+    };
+    diagonal.target = function(x) {
+      if (!arguments.length) return target;
+      target = d3_functor(x);
+      return diagonal;
+    };
+    diagonal.projection = function(x) {
+      if (!arguments.length) return projection;
+      projection = x;
+      return diagonal;
+    };
+    return diagonal;
+  };
+  function d3_svg_diagonalProjection(d) {
+    return [ d.x, d.y ];
+  }
+  d3.svg.diagonal.radial = function() {
+    var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection;
+    diagonal.projection = function(x) {
+      return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection;
+    };
+    return diagonal;
+  };
+  function d3_svg_diagonalRadialProjection(projection) {
+    return function() {
+      var d = projection.apply(this, arguments), r = d[0], a = d[1] + d3_svg_arcOffset;
+      return [ r * Math.cos(a), r * Math.sin(a) ];
+    };
+  }
+  d3.svg.symbol = function() {
+    var type = d3_svg_symbolType, size = d3_svg_symbolSize;
+    function symbol(d, i) {
+      return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i));
+    }
+    symbol.type = function(x) {
+      if (!arguments.length) return type;
+      type = d3_functor(x);
+      return symbol;
+    };
+    symbol.size = function(x) {
+      if (!arguments.length) return size;
+      size = d3_functor(x);
+      return symbol;
+    };
+    return symbol;
+  };
+  function d3_svg_symbolSize() {
+    return 64;
+  }
+  function d3_svg_symbolType() {
+    return "circle";
+  }
+  function d3_svg_symbolCircle(size) {
+    var r = Math.sqrt(size / Ï);
+    return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z";
+  }
+  var d3_svg_symbols = d3.map({
+    circle: d3_svg_symbolCircle,
+    cross: function(size) {
+      var r = Math.sqrt(size / 5) / 2;
+      return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z";
+    },
+    diamond: function(size) {
+      var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30;
+      return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z";
+    },
+    square: function(size) {
+      var r = Math.sqrt(size) / 2;
+      return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z";
+    },
+    "triangle-down": function(size) {
+      var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
+      return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z";
+    },
+    "triangle-up": function(size) {
+      var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
+      return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z";
+    }
+  });
+  d3.svg.symbolTypes = d3_svg_symbols.keys();
+  var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians);
+  function d3_transition(groups, id) {
+    d3_subclass(groups, d3_transitionPrototype);
+    groups.id = id;
+    return groups;
+  }
+  var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit;
+  d3_transitionPrototype.call = d3_selectionPrototype.call;
+  d3_transitionPrototype.empty = d3_selectionPrototype.empty;
+  d3_transitionPrototype.node = d3_selectionPrototype.node;
+  d3_transitionPrototype.size = d3_selectionPrototype.size;
+  d3.transition = function(selection) {
+    return arguments.length ? d3_transitionInheritId ? selection.transition() : selection : d3_selectionRoot.transition();
+  };
+  d3.transition.prototype = d3_transitionPrototype;
+  d3_transitionPrototype.select = function(selector) {
+    var id = this.id, subgroups = [], subgroup, subnode, node;
+    selector = d3_selection_selector(selector);
+    for (var j = -1, m = this.length; ++j < m; ) {
+      subgroups.push(subgroup = []);
+      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+        if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) {
+          if ("__data__" in node) subnode.__data__ = node.__data__;
+          d3_transitionNode(subnode, i, id, node.__transition__[id]);
+          subgroup.push(subnode);
+        } else {
+          subgroup.push(null);
+        }
+      }
+    }
+    return d3_transition(subgroups, id);
+  };
+  d3_transitionPrototype.selectAll = function(selector) {
+    var id = this.id, subgroups = [], subgroup, subnodes, node, subnode, transition;
+    selector = d3_selection_selectorAll(selector);
+    for (var j = -1, m = this.length; ++j < m; ) {
+      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+        if (node = group[i]) {
+          transition = node.__transition__[id];
+          subnodes = selector.call(node, node.__data__, i, j);
+          subgroups.push(subgroup = []);
+          for (var k = -1, o = subnodes.length; ++k < o; ) {
+            if (subnode = subnodes[k]) d3_transitionNode(subnode, k, id, transition);
+            subgroup.push(subnode);
+          }
+        }
+      }
+    }
+    return d3_transition(subgroups, id);
+  };
+  d3_transitionPrototype.filter = function(filter) {
+    var subgroups = [], subgroup, group, node;
+    if (typeof filter !== "function") filter = d3_selection_filter(filter);
+    for (var j = 0, m = this.length; j < m; j++) {
+      subgroups.push(subgroup = []);
+      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+        if ((node = group[i]) && filter.call(node, node.__data__, i)) {
+          subgroup.push(node);
+        }
+      }
+    }
+    return d3_transition(subgroups, this.id);
+  };
+  d3_transitionPrototype.tween = function(name, tween) {
+    var id = this.id;
+    if (arguments.length < 2) return this.node().__transition__[id].tween.get(name);
+    return d3_selection_each(this, tween == null ? function(node) {
+      node.__transition__[id].tween.remove(name);
+    } : function(node) {
+      node.__transition__[id].tween.set(name, tween);
+    });
+  };
+  function d3_transition_tween(groups, name, value, tween) {
+    var id = groups.id;
+    return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) {
+      node.__transition__[id].tween.set(name, tween(value.call(node, node.__data__, i, j)));
+    } : (value = tween(value), function(node) {
+      node.__transition__[id].tween.set(name, value);
+    }));
+  }
+  d3_transitionPrototype.attr = function(nameNS, value) {
+    if (arguments.length < 2) {
+      for (value in nameNS) this.attr(value, nameNS[value]);
+      return this;
+    }
+    var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS);
+    function attrNull() {
+      this.removeAttribute(name);
+    }
+    function attrNullNS() {
+      this.removeAttributeNS(name.space, name.local);
+    }
+    function attrTween(b) {
+      return b == null ? attrNull : (b += "", function() {
+        var a = this.getAttribute(name), i;
+        return a !== b && (i = interpolate(a, b), function(t) {
+          this.setAttribute(name, i(t));
+        });
+      });
+    }
+    function attrTweenNS(b) {
+      return b == null ? attrNullNS : (b += "", function() {
+        var a = this.getAttributeNS(name.space, name.local), i;
+        return a !== b && (i = interpolate(a, b), function(t) {
+          this.setAttributeNS(name.space, name.local, i(t));
+        });
+      });
+    }
+    return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween);
+  };
+  d3_transitionPrototype.attrTween = function(nameNS, tween) {
+    var name = d3.ns.qualify(nameNS);
+    function attrTween(d, i) {
+      var f = tween.call(this, d, i, this.getAttribute(name));
+      return f && function(t) {
+        this.setAttribute(name, f(t));
+      };
+    }
+    function attrTweenNS(d, i) {
+      var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
+      return f && function(t) {
+        this.setAttributeNS(name.space, name.local, f(t));
+      };
+    }
+    return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
+  };
+  d3_transitionPrototype.style = function(name, value, priority) {
+    var n = arguments.length;
+    if (n < 3) {
+      if (typeof name !== "string") {
+        if (n < 2) value = "";
+        for (priority in name) this.style(priority, name[priority], value);
+        return this;
+      }
+      priority = "";
+    }
+    function styleNull() {
+      this.style.removeProperty(name);
+    }
+    function styleString(b) {
+      return b == null ? styleNull : (b += "", function() {
+        var a = d3_window.getComputedStyle(this, null).getPropertyValue(name), i;
+        return a !== b && (i = d3_interpolate(a, b), function(t) {
+          this.style.setProperty(name, i(t), priority);
+        });
+      });
+    }
+    return d3_transition_tween(this, "style." + name, value, styleString);
+  };
+  d3_transitionPrototype.styleTween = function(name, tween, priority) {
+    if (arguments.length < 3) priority = "";
+    function styleTween(d, i) {
+      var f = tween.call(this, d, i, d3_window.getComputedStyle(this, null).getPropertyValue(name));
+      return f && function(t) {
+        this.style.setProperty(name, f(t), priority);
+      };
+    }
+    return this.tween("style." + name, styleTween);
+  };
+  d3_transitionPrototype.text = function(value) {
+    return d3_transition_tween(this, "text", value, d3_transition_text);
+  };
+  function d3_transition_text(b) {
+    if (b == null) b = "";
+    return function() {
+      this.textContent = b;
+    };
+  }
+  d3_transitionPrototype.remove = function() {
+    return this.each("end.transition", function() {
+      var p;
+      if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this);
+    });
+  };
+  d3_transitionPrototype.ease = function(value) {
+    var id = this.id;
+    if (arguments.length < 1) return this.node().__transition__[id].ease;
+    if (typeof value !== "function") value = d3.ease.apply(d3, arguments);
+    return d3_selection_each(this, function(node) {
+      node.__transition__[id].ease = value;
+    });
+  };
+  d3_transitionPrototype.delay = function(value) {
+    var id = this.id;
+    return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
+      node.__transition__[id].delay = value.call(node, node.__data__, i, j) | 0;
+    } : (value |= 0, function(node) {
+      node.__transition__[id].delay = value;
+    }));
+  };
+  d3_transitionPrototype.duration = function(value) {
+    var id = this.id;
+    return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
+      node.__transition__[id].duration = Math.max(1, value.call(node, node.__data__, i, j) | 0);
+    } : (value = Math.max(1, value | 0), function(node) {
+      node.__transition__[id].duration = value;
+    }));
+  };
+  d3_transitionPrototype.each = function(type, listener) {
+    var id = this.id;
+    if (arguments.length < 2) {
+      var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId;
+      d3_transitionInheritId = id;
+      d3_selection_each(this, function(node, i, j) {
+        d3_transitionInherit = node.__transition__[id];
+        type.call(node, node.__data__, i, j);
+      });
+      d3_transitionInherit = inherit;
+      d3_transitionInheritId = inheritId;
+    } else {
+      d3_selection_each(this, function(node) {
+        var transition = node.__transition__[id];
+        (transition.event || (transition.event = d3.dispatch("start", "end"))).on(type, listener);
+      });
+    }
+    return this;
+  };
+  d3_transitionPrototype.transition = function() {
+    var id0 = this.id, id1 = ++d3_transitionId, subgroups = [], subgroup, group, node, transition;
+    for (var j = 0, m = this.length; j < m; j++) {
+      subgroups.push(subgroup = []);
+      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+        if (node = group[i]) {
+          transition = Object.create(node.__transition__[id0]);
+          transition.delay += transition.duration;
+          d3_transitionNode(node, i, id1, transition);
+        }
+        subgroup.push(node);
+      }
+    }
+    return d3_transition(subgroups, id1);
+  };
+  function d3_transitionNode(node, i, id, inherit) {
+    var lock = node.__transition__ || (node.__transition__ = {
+      active: 0,
+      count: 0
+    }), transition = lock[id];
+    if (!transition) {
+      var time = inherit.time;
+      transition = lock[id] = {
+        tween: new d3_Map(),
+        time: time,
+        ease: inherit.ease,
+        delay: inherit.delay,
+        duration: inherit.duration
+      };
+      ++lock.count;
+      d3.timer(function(elapsed) {
+        var d = node.__data__, ease = transition.ease, delay = transition.delay, duration = transition.duration, tweened = [];
+        if (delay <= elapsed) return start(elapsed);
+        d3_timer_replace(start, delay, time);
+        function start(elapsed) {
+          if (lock.active > id) return stop();
+          lock.active = id;
+          transition.event && transition.event.start.call(node, d, i);
+          transition.tween.forEach(function(key, value) {
+            if (value = value.call(node, d, i)) {
+              tweened.push(value);
+            }
+          });
+          if (tick(elapsed)) return 1;
+          d3_timer_replace(tick, 0, time);
+        }
+        function tick(elapsed) {
+          if (lock.active !== id) return stop();
+          var t = (elapsed - delay) / duration, e = ease(t), n = tweened.length;
+          while (n > 0) {
+            tweened[--n].call(node, e);
+          }
+          if (t >= 1) {
+            stop();
+            transition.event && transition.event.end.call(node, d, i);
+            return 1;
+          }
+        }
+        function stop() {
+          if (--lock.count) delete lock[id]; else delete node.__transition__;
+          return 1;
+        }
+      }, 0, time);
+    }
+  }
+  d3.svg.axis = function() {
+    var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, tickMajorSize = 6, tickMinorSize = 6, tickEndSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_, tickSubdivide = 0;
+    function axis(g) {
+      g.each(function() {
+        var g = d3.select(this);
+        var ticks = tickValues == null ? scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain() : tickValues, tickFormat = tickFormat_ == null ? scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String : tickFormat_;
+        var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide), subtick = g.selectAll(".tick.minor").data(subticks, String), subtickEnter = subtick.enter().insert("line", ".tick").attr("class", "tick minor").style("opacity", 1e-6), subtickExit = d3.transition(subtick.exit()).style("opacity", 1e-6).remove(), subtickUpdate = d3.transition(subtick).style("opacity", 1);
+        var tick = g.selectAll(".tick.major").data(ticks, String), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick major").style("opacity", 1e-6), tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(), tickUpdate = d3.transition(tick).style("opacity", 1), tickTransform;
+        var range = d3_scaleRange(scale), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), 
+        d3.transition(path));
+        var scale1 = scale.copy(), scale0 = this.__chart__ || scale1;
+        this.__chart__ = scale1;
+        tickEnter.append("line");
+        tickEnter.append("text");
+        var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text");
+        switch (orient) {
+         case "bottom":
+          {
+            tickTransform = d3_svg_axisX;
+            subtickEnter.attr("y2", tickMinorSize);
+            subtickUpdate.attr("x2", 0).attr("y2", tickMinorSize);
+            lineEnter.attr("y2", tickMajorSize);
+            textEnter.attr("y", Math.max(tickMajorSize, 0) + tickPadding);
+            lineUpdate.attr("x2", 0).attr("y2", tickMajorSize);
+            textUpdate.attr("x", 0).attr("y", Math.max(tickMajorSize, 0) + tickPadding);
+            text.attr("dy", ".71em").style("text-anchor", "middle");
+            pathUpdate.attr("d", "M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize);
+            break;
+          }
+
+         case "top":
+          {
+            tickTransform = d3_svg_axisX;
+            subtickEnter.attr("y2", -tickMinorSize);
+            subtickUpdate.attr("x2", 0).attr("y2", -tickMinorSize);
+            lineEnter.attr("y2", -tickMajorSize);
+            textEnter.attr("y", -(Math.max(tickMajorSize, 0) + tickPadding));
+            lineUpdate.attr("x2", 0).attr("y2", -tickMajorSize);
+            textUpdate.attr("x", 0).attr("y", -(Math.max(tickMajorSize, 0) + tickPadding));
+            text.attr("dy", "0em").style("text-anchor", "middle");
+            pathUpdate.attr("d", "M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize);
+            break;
+          }
+
+         case "left":
+          {
+            tickTransform = d3_svg_axisY;
+            subtickEnter.attr("x2", -tickMinorSize);
+            subtickUpdate.attr("x2", -tickMinorSize).attr("y2", 0);
+            lineEnter.attr("x2", -tickMajorSize);
+            textEnter.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding));
+            lineUpdate.attr("x2", -tickMajorSize).attr("y2", 0);
+            textUpdate.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)).attr("y", 0);
+            text.attr("dy", ".32em").style("text-anchor", "end");
+            pathUpdate.attr("d", "M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize);
+            break;
+          }
+
+         case "right":
+          {
+            tickTransform = d3_svg_axisY;
+            subtickEnter.attr("x2", tickMinorSize);
+            subtickUpdate.attr("x2", tickMinorSize).attr("y2", 0);
+            lineEnter.attr("x2", tickMajorSize);
+            textEnter.attr("x", Math.max(tickMajorSize, 0) + tickPadding);
+            lineUpdate.attr("x2", tickMajorSize).attr("y2", 0);
+            textUpdate.attr("x", Math.max(tickMajorSize, 0) + tickPadding).attr("y", 0);
+            text.attr("dy", ".32em").style("text-anchor", "start");
+            pathUpdate.attr("d", "M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize);
+            break;
+          }
+        }
+        if (scale.rangeBand) {
+          var dx = scale1.rangeBand() / 2, x = function(d) {
+            return scale1(d) + dx;
+          };
+          tickEnter.call(tickTransform, x);
+          tickUpdate.call(tickTransform, x);
+        } else {
+          tickEnter.call(tickTransform, scale0);
+          tickUpdate.call(tickTransform, scale1);
+          tickExit.call(tickTransform, scale1);
+          subtickEnter.call(tickTransform, scale0);
+          subtickUpdate.call(tickTransform, scale1);
+          subtickExit.call(tickTransform, scale1);
+        }
+      });
+    }
+    axis.scale = function(x) {
+      if (!arguments.length) return scale;
+      scale = x;
+      return axis;
+    };
+    axis.orient = function(x) {
+      if (!arguments.length) return orient;
+      orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient;
+      return axis;
+    };
+    axis.ticks = function() {
+      if (!arguments.length) return tickArguments_;
+      tickArguments_ = arguments;
+      return axis;
+    };
+    axis.tickValues = function(x) {
+      if (!arguments.length) return tickValues;
+      tickValues = x;
+      return axis;
+    };
+    axis.tickFormat = function(x) {
+      if (!arguments.length) return tickFormat_;
+      tickFormat_ = x;
+      return axis;
+    };
+    axis.tickSize = function(x, y) {
+      if (!arguments.length) return tickMajorSize;
+      var n = arguments.length - 1;
+      tickMajorSize = +x;
+      tickMinorSize = n > 1 ? +y : tickMajorSize;
+      tickEndSize = n > 0 ? +arguments[n] : tickMajorSize;
+      return axis;
+    };
+    axis.tickPadding = function(x) {
+      if (!arguments.length) return tickPadding;
+      tickPadding = +x;
+      return axis;
+    };
+    axis.tickSubdivide = function(x) {
+      if (!arguments.length) return tickSubdivide;
+      tickSubdivide = +x;
+      return axis;
+    };
+    return axis;
+  };
+  var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = {
+    top: 1,
+    right: 1,
+    bottom: 1,
+    left: 1
+  };
+  function d3_svg_axisX(selection, x) {
+    selection.attr("transform", function(d) {
+      return "translate(" + x(d) + ",0)";
+    });
+  }
+  function d3_svg_axisY(selection, y) {
+    selection.attr("transform", function(d) {
+      return "translate(0," + y(d) + ")";
+    });
+  }
+  function d3_svg_axisSubdivide(scale, ticks, m) {
+    subticks = [];
+    if (m && ticks.length > 1) {
+      var extent = d3_scaleExtent(scale.domain()), subticks, i = -1, n = ticks.length, d = (ticks[1] - ticks[0]) / ++m, j, v;
+      while (++i < n) {
+        for (j = m; --j > 0; ) {
+          if ((v = +ticks[i] - j * d) >= extent[0]) {
+            subticks.push(v);
+          }
+        }
+      }
+      for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1]; ) {
+        subticks.push(v);
+      }
+    }
+    return subticks;
+  }
+  d3.svg.brush = function() {
+    var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, resizes = d3_svg_brushResizes[0], extent = [ [ 0, 0 ], [ 0, 0 ] ], clamp = [ true, true ], extentDomain;
+    function brush(g) {
+      g.each(function() {
+        var g = d3.select(this), bg = g.selectAll(".background").data([ 0 ]), fg = g.selectAll(".extent").data([ 0 ]), tz = g.selectAll(".resize").data(resizes, String), e;
+        g.style("pointer-events", "all").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart);
+        bg.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair");
+        fg.enter().append("rect").attr("class", "extent").style("cursor", "move");
+        tz.enter().append("g").attr("class", function(d) {
+          return "resize " + d;
+        }).style("cursor", function(d) {
+          return d3_svg_brushCursor[d];
+        }).append("rect").attr("x", function(d) {
+          return /[ew]$/.test(d) ? -3 : null;
+        }).attr("y", function(d) {
+          return /^[ns]/.test(d) ? -3 : null;
+        }).attr("width", 6).attr("height", 6).style("visibility", "hidden");
+        tz.style("display", brush.empty() ? "none" : null);
+        tz.exit().remove();
+        if (x) {
+          e = d3_scaleRange(x);
+          bg.attr("x", e[0]).attr("width", e[1] - e[0]);
+          redrawX(g);
+        }
+        if (y) {
+          e = d3_scaleRange(y);
+          bg.attr("y", e[0]).attr("height", e[1] - e[0]);
+          redrawY(g);
+        }
+        redraw(g);
+      });
+    }
+    function redraw(g) {
+      g.selectAll(".resize").attr("transform", function(d) {
+        return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")";
+      });
+    }
+    function redrawX(g) {
+      g.select(".extent").attr("x", extent[0][0]);
+      g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]);
+    }
+    function redrawY(g) {
+      g.select(".extent").attr("y", extent[0][1]);
+      g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]);
+    }
+    function brushstart() {
+      var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(), center, origin = mouse(), offset;
+      var w = d3.select(d3_window).on("keydown.brush", keydown).on("keyup.brush", keyup);
+      if (d3.event.changedTouches) {
+        w.on("touchmove.brush", brushmove).on("touchend.brush", brushend);
+      } else {
+        w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend);
+      }
+      if (dragging) {
+        origin[0] = extent[0][0] - origin[0];
+        origin[1] = extent[0][1] - origin[1];
+      } else if (resizing) {
+        var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing);
+        offset = [ extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1] ];
+        origin[0] = extent[ex][0];
+        origin[1] = extent[ey][1];
+      } else if (d3.event.altKey) center = origin.slice();
+      g.style("pointer-events", "none").selectAll(".resize").style("display", null);
+      d3.select("body").style("cursor", eventTarget.style("cursor"));
+      event_({
+        type: "brushstart"
+      });
+      brushmove();
+      function mouse() {
+        var touches = d3.event.changedTouches;
+        return touches ? d3.touches(target, touches)[0] : d3.mouse(target);
+      }
+      function keydown() {
+        if (d3.event.keyCode == 32) {
+          if (!dragging) {
+            center = null;
+            origin[0] -= extent[1][0];
+            origin[1] -= extent[1][1];
+            dragging = 2;
+          }
+          d3_eventPreventDefault();
+        }
+      }
+      function keyup() {
+        if (d3.event.keyCode == 32 && dragging == 2) {
+          origin[0] += extent[1][0];
+          origin[1] += extent[1][1];
+          dragging = 0;
+          d3_eventPreventDefault();
+        }
+      }
+      function brushmove() {
+        var point = mouse(), moved = false;
+        if (offset) {
+          point[0] += offset[0];
+          point[1] += offset[1];
+        }
+        if (!dragging) {
+          if (d3.event.altKey) {
+            if (!center) center = [ (extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2 ];
+            origin[0] = extent[+(point[0] < center[0])][0];
+            origin[1] = extent[+(point[1] < center[1])][1];
+          } else center = null;
+        }
+        if (resizingX && move1(point, x, 0)) {
+          redrawX(g);
+          moved = true;
+        }
+        if (resizingY && move1(point, y, 1)) {
+          redrawY(g);
+          moved = true;
+        }
+        if (moved) {
+          redraw(g);
+          event_({
+            type: "brush",
+            mode: dragging ? "move" : "resize"
+          });
+        }
+      }
+      function move1(point, scale, i) {
+        var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], size = extent[1][i] - extent[0][i], min, max;
+        if (dragging) {
+          r0 -= position;
+          r1 -= size + position;
+        }
+        min = clamp[i] ? Math.max(r0, Math.min(r1, point[i])) : point[i];
+        if (dragging) {
+          max = (min += position) + size;
+        } else {
+          if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min));
+          if (position < min) {
+            max = min;
+            min = position;
+          } else {
+            max = position;
+          }
+        }
+        if (extent[0][i] !== min || extent[1][i] !== max) {
+          extentDomain = null;
+          extent[0][i] = min;
+          extent[1][i] = max;
+          return true;
+        }
+      }
+      function brushend() {
+        brushmove();
+        g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null);
+        d3.select("body").style("cursor", null);
+        w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null);
+        dragRestore();
+        event_({
+          type: "brushend"
+        });
+      }
+    }
+    brush.x = function(z) {
+      if (!arguments.length) return x;
+      x = z;
+      resizes = d3_svg_brushResizes[!x << 1 | !y];
+      return brush;
+    };
+    brush.y = function(z) {
+      if (!arguments.length) return y;
+      y = z;
+      resizes = d3_svg_brushResizes[!x << 1 | !y];
+      return brush;
+    };
+    brush.clamp = function(z) {
+      if (!arguments.length) return x && y ? clamp : x || y ? clamp[+!x] : null;
+      if (x && y) clamp = [ !!z[0], !!z[1] ]; else if (x || y) clamp[+!x] = !!z;
+      return brush;
+    };
+    brush.extent = function(z) {
+      var x0, x1, y0, y1, t;
+      if (!arguments.length) {
+        z = extentDomain || extent;
+        if (x) {
+          x0 = z[0][0], x1 = z[1][0];
+          if (!extentDomain) {
+            x0 = extent[0][0], x1 = extent[1][0];
+            if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
+            if (x1 < x0) t = x0, x0 = x1, x1 = t;
+          }
+        }
+        if (y) {
+          y0 = z[0][1], y1 = z[1][1];
+          if (!extentDomain) {
+            y0 = extent[0][1], y1 = extent[1][1];
+            if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
+            if (y1 < y0) t = y0, y0 = y1, y1 = t;
+          }
+        }
+        return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ];
+      }
+      extentDomain = [ [ 0, 0 ], [ 0, 0 ] ];
+      if (x) {
+        x0 = z[0], x1 = z[1];
+        if (y) x0 = x0[0], x1 = x1[0];
+        extentDomain[0][0] = x0, extentDomain[1][0] = x1;
+        if (x.invert) x0 = x(x0), x1 = x(x1);
+        if (x1 < x0) t = x0, x0 = x1, x1 = t;
+        extent[0][0] = x0 | 0, extent[1][0] = x1 | 0;
+      }
+      if (y) {
+        y0 = z[0], y1 = z[1];
+        if (x) y0 = y0[1], y1 = y1[1];
+        extentDomain[0][1] = y0, extentDomain[1][1] = y1;
+        if (y.invert) y0 = y(y0), y1 = y(y1);
+        if (y1 < y0) t = y0, y0 = y1, y1 = t;
+        extent[0][1] = y0 | 0, extent[1][1] = y1 | 0;
+      }
+      return brush;
+    };
+    brush.clear = function() {
+      extentDomain = null;
+      extent[0][0] = extent[0][1] = extent[1][0] = extent[1][1] = 0;
+      return brush;
+    };
+    brush.empty = function() {
+      return x && extent[0][0] === extent[1][0] || y && extent[0][1] === extent[1][1];
+    };
+    return d3.rebind(brush, event, "on");
+  };
+  var d3_svg_brushCursor = {
+    n: "ns-resize",
+    e: "ew-resize",
+    s: "ns-resize",
+    w: "ew-resize",
+    nw: "nwse-resize",
+    ne: "nesw-resize",
+    se: "nwse-resize",
+    sw: "nesw-resize"
+  };
+  var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ];
+  d3.time = {};
+  var d3_time = Date, d3_time_daySymbols = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ];
+  function d3_time_utc() {
+    this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]);
+  }
+  d3_time_utc.prototype = {
+    getDate: function() {
+      return this._.getUTCDate();
+    },
+    getDay: function() {
+      return this._.getUTCDay();
+    },
+    getFullYear: function() {
+      return this._.getUTCFullYear();
+    },
+    getHours: function() {
+      return this._.getUTCHours();
+    },
+    getMilliseconds: function() {
+      return this._.getUTCMilliseconds();
+    },
+    getMinutes: function() {
+      return this._.getUTCMinutes();
+    },
+    getMonth: function() {
+      return this._.getUTCMonth();
+    },
+    getSeconds: function() {
+      return this._.getUTCSeconds();
+    },
+    getTime: function() {
+      return this._.getTime();
+    },
+    getTimezoneOffset: function() {
+      return 0;
+    },
+    valueOf: function() {
+      return this._.valueOf();
+    },
+    setDate: function() {
+      d3_time_prototype.setUTCDate.apply(this._, arguments);
+    },
+    setDay: function() {
+      d3_time_prototype.setUTCDay.apply(this._, arguments);
+    },
+    setFullYear: function() {
+      d3_time_prototype.setUTCFullYear.apply(this._, arguments);
+    },
+    setHours: function() {
+      d3_time_prototype.setUTCHours.apply(this._, arguments);
+    },
+    setMilliseconds: function() {
+      d3_time_prototype.setUTCMilliseconds.apply(this._, arguments);
+    },
+    setMinutes: function() {
+      d3_time_prototype.setUTCMinutes.apply(this._, arguments);
+    },
+    setMonth: function() {
+      d3_time_prototype.setUTCMonth.apply(this._, arguments);
+    },
+    setSeconds: function() {
+      d3_time_prototype.setUTCSeconds.apply(this._, arguments);
+    },
+    setTime: function() {
+      d3_time_prototype.setTime.apply(this._, arguments);
+    }
+  };
+  var d3_time_prototype = Date.prototype;
+  var d3_time_formatDateTime = "%a %b %e %X %Y", d3_time_formatDate = "%m/%d/%Y", d3_time_formatTime = "%H:%M:%S";
+  var d3_time_days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], d3_time_dayAbbreviations = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], d3_time_months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], d3_time_monthAbbreviations = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];
+  function d3_time_interval(local, step, number) {
+    function round(date) {
+      var d0 = local(date), d1 = offset(d0, 1);
+      return date - d0 < d1 - date ? d0 : d1;
+    }
+    function ceil(date) {
+      step(date = local(new d3_time(date - 1)), 1);
+      return date;
+    }
+    function offset(date, k) {
+      step(date = new d3_time(+date), k);
+      return date;
+    }
+    function range(t0, t1, dt) {
+      var time = ceil(t0), times = [];
+      if (dt > 1) {
+        while (time < t1) {
+          if (!(number(time) % dt)) times.push(new Date(+time));
+          step(time, 1);
+        }
+      } else {
+        while (time < t1) times.push(new Date(+time)), step(time, 1);
+      }
+      return times;
+    }
+    function range_utc(t0, t1, dt) {
+      try {
+        d3_time = d3_time_utc;
+        var utc = new d3_time_utc();
+        utc._ = t0;
+        return range(utc, t1, dt);
+      } finally {
+        d3_time = Date;
+      }
+    }
+    local.floor = local;
+    local.round = round;
+    local.ceil = ceil;
+    local.offset = offset;
+    local.range = range;
+    var utc = local.utc = d3_time_interval_utc(local);
+    utc.floor = utc;
+    utc.round = d3_time_interval_utc(round);
+    utc.ceil = d3_time_interval_utc(ceil);
+    utc.offset = d3_time_interval_utc(offset);
+    utc.range = range_utc;
+    return local;
+  }
+  function d3_time_interval_utc(method) {
+    return function(date, k) {
+      try {
+        d3_time = d3_time_utc;
+        var utc = new d3_time_utc();
+        utc._ = date;
+        return method(utc, k)._;
+      } finally {
+        d3_time = Date;
+      }
+    };
+  }
+  d3.time.year = d3_time_interval(function(date) {
+    date = d3.time.day(date);
+    date.setMonth(0, 1);
+    return date;
+  }, function(date, offset) {
+    date.setFullYear(date.getFullYear() + offset);
+  }, function(date) {
+    return date.getFullYear();
+  });
+  d3.time.years = d3.time.year.range;
+  d3.time.years.utc = d3.time.year.utc.range;
+  d3.time.day = d3_time_interval(function(date) {
+    var day = new d3_time(2e3, 0);
+    day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
+    return day;
+  }, function(date, offset) {
+    date.setDate(date.getDate() + offset);
+  }, function(date) {
+    return date.getDate() - 1;
+  });
+  d3.time.days = d3.time.day.range;
+  d3.time.days.utc = d3.time.day.utc.range;
+  d3.time.dayOfYear = function(date) {
+    var year = d3.time.year(date);
+    return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5);
+  };
+  d3_time_daySymbols.forEach(function(day, i) {
+    day = day.toLowerCase();
+    i = 7 - i;
+    var interval = d3.time[day] = d3_time_interval(function(date) {
+      (date = d3.time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7);
+      return date;
+    }, function(date, offset) {
+      date.setDate(date.getDate() + Math.floor(offset) * 7);
+    }, function(date) {
+      var day = d3.time.year(date).getDay();
+      return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i);
+    });
+    d3.time[day + "s"] = interval.range;
+    d3.time[day + "s"].utc = interval.utc.range;
+    d3.time[day + "OfYear"] = function(date) {
+      var day = d3.time.year(date).getDay();
+      return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7);
+    };
+  });
+  d3.time.week = d3.time.sunday;
+  d3.time.weeks = d3.time.sunday.range;
+  d3.time.weeks.utc = d3.time.sunday.utc.range;
+  d3.time.weekOfYear = d3.time.sundayOfYear;
+  d3.time.format = function(template) {
+    var n = template.length;
+    function format(date) {
+      var string = [], i = -1, j = 0, c, p, f;
+      while (++i < n) {
+        if (template.charCodeAt(i) === 37) {
+          string.push(template.substring(j, i));
+          if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i);
+          if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p);
+          string.push(c);
+          j = i + 1;
+        }
+      }
+      string.push(template.substring(j, i));
+      return string.join("");
+    }
+    format.parse = function(string) {
+      var d = {
+        y: 1900,
+        m: 0,
+        d: 1,
+        H: 0,
+        M: 0,
+        S: 0,
+        L: 0
+      }, i = d3_time_parse(d, template, string, 0);
+      if (i != string.length) return null;
+      if ("p" in d) d.H = d.H % 12 + d.p * 12;
+      var date = new d3_time();
+      if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("w" in d && ("W" in d || "U" in d)) {
+        date.setFullYear(d.y, 0, 1);
+        date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7);
+      } else date.setFullYear(d.y, d.m, d.d);
+      date.setHours(d.H, d.M, d.S, d.L);
+      return date;
+    };
+    format.toString = function() {
+      return template;
+    };
+    return format;
+  };
+  function d3_time_parse(date, template, string, j) {
+    var c, p, i = 0, n = template.length, m = string.length;
+    while (i < n) {
+      if (j >= m) return -1;
+      c = template.charCodeAt(i++);
+      if (c === 37) {
+        p = d3_time_parsers[template.charAt(i++)];
+        if (!p || (j = p(date, string, j)) < 0) return -1;
+      } else if (c != string.charCodeAt(j++)) {
+        return -1;
+      }
+    }
+    return j;
+  }
+  function d3_time_formatRe(names) {
+    return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i");
+  }
+  function d3_time_formatLookup(names) {
+    var map = new d3_Map(), i = -1, n = names.length;
+    while (++i < n) map.set(names[i].toLowerCase(), i);
+    return map;
+  }
+  function d3_time_formatPad(value, fill, width) {
+    var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length;
+    return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);
+  }
+  var d3_time_dayRe = d3_time_formatRe(d3_time_days), d3_time_dayLookup = d3_time_formatLookup(d3_time_days), d3_time_dayAbbrevRe = d3_time_formatRe(d3_time_dayAbbreviations), d3_time_dayAbbrevLookup = d3_time_formatLookup(d3_time_dayAbbreviations), d3_time_monthRe = d3_time_formatRe(d3_time_months), d3_time_monthLookup = d3_time_formatLookup(d3_time_months), d3_time_monthAbbrevRe = d3_time_formatRe(d3_time_monthAbbreviations), d3_time_monthAbbrevLookup = d3_time_formatLookup(d3_time_monthAbbreviations), d3_time_percentRe = /^%/;
+  var d3_time_formatPads = {
+    "-": "",
+    _: " ",
+    "0": "0"
+  };
+  var d3_time_formats = {
+    a: function(d) {
+      return d3_time_dayAbbreviations[d.getDay()];
+    },
+    A: function(d) {
+      return d3_time_days[d.getDay()];
+    },
+    b: function(d) {
+      return d3_time_monthAbbreviations[d.getMonth()];
+    },
+    B: function(d) {
+      return d3_time_months[d.getMonth()];
+    },
+    c: d3.time.format(d3_time_formatDateTime),
+    d: function(d, p) {
+      return d3_time_formatPad(d.getDate(), p, 2);
+    },
+    e: function(d, p) {
+      return d3_time_formatPad(d.getDate(), p, 2);
+    },
+    H: function(d, p) {
+      return d3_time_formatPad(d.getHours(), p, 2);
+    },
+    I: function(d, p) {
+      return d3_time_formatPad(d.getHours() % 12 || 12, p, 2);
+    },
+    j: function(d, p) {
+      return d3_time_formatPad(1 + d3.time.dayOfYear(d), p, 3);
+    },
+    L: function(d, p) {
+      return d3_time_formatPad(d.getMilliseconds(), p, 3);
+    },
+    m: function(d, p) {
+      return d3_time_formatPad(d.getMonth() + 1, p, 2);
+    },
+    M: function(d, p) {
+      return d3_time_formatPad(d.getMinutes(), p, 2);
+    },
+    p: function(d) {
+      return d.getHours() >= 12 ? "PM" : "AM";
+    },
+    S: function(d, p) {
+      return d3_time_formatPad(d.getSeconds(), p, 2);
+    },
+    U: function(d, p) {
+      return d3_time_formatPad(d3.time.sundayOfYear(d), p, 2);
+    },
+    w: function(d) {
+      return d.getDay();
+    },
+    W: function(d, p) {
+      return d3_time_formatPad(d3.time.mondayOfYear(d), p, 2);
+    },
+    x: d3.time.format(d3_time_formatDate),
+    X: d3.time.format(d3_time_formatTime),
+    y: function(d, p) {
+      return d3_time_formatPad(d.getFullYear() % 100, p, 2);
+    },
+    Y: function(d, p) {
+      return d3_time_formatPad(d.getFullYear() % 1e4, p, 4);
+    },
+    Z: d3_time_zone,
+    "%": function() {
+      return "%";
+    }
+  };
+  var d3_time_parsers = {
+    a: d3_time_parseWeekdayAbbrev,
+    A: d3_time_parseWeekday,
+    b: d3_time_parseMonthAbbrev,
+    B: d3_time_parseMonth,
+    c: d3_time_parseLocaleFull,
+    d: d3_time_parseDay,
+    e: d3_time_parseDay,
+    H: d3_time_parseHour24,
+    I: d3_time_parseHour24,
+    j: d3_time_parseDayOfYear,
+    L: d3_time_parseMilliseconds,
+    m: d3_time_parseMonthNumber,
+    M: d3_time_parseMinutes,
+    p: d3_time_parseAmPm,
+    S: d3_time_parseSeconds,
+    U: d3_time_parseWeekNumberSunday,
+    w: d3_time_parseWeekdayNumber,
+    W: d3_time_parseWeekNumberMonday,
+    x: d3_time_parseLocaleDate,
+    X: d3_time_parseLocaleTime,
+    y: d3_time_parseYear,
+    Y: d3_time_parseFullYear,
+    "%": d3_time_parseLiteralPercent
+  };
+  function d3_time_parseWeekdayAbbrev(date, string, i) {
+    d3_time_dayAbbrevRe.lastIndex = 0;
+    var n = d3_time_dayAbbrevRe.exec(string.substring(i));
+    return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
+  }
+  function d3_time_parseWeekday(date, string, i) {
+    d3_time_dayRe.lastIndex = 0;
+    var n = d3_time_dayRe.exec(string.substring(i));
+    return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
+  }
+  function d3_time_parseWeekdayNumber(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.substring(i, i + 1));
+    return n ? (date.w = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseWeekNumberSunday(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.substring(i));
+    return n ? (date.U = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseWeekNumberMonday(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.substring(i));
+    return n ? (date.W = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseMonthAbbrev(date, string, i) {
+    d3_time_monthAbbrevRe.lastIndex = 0;
+    var n = d3_time_monthAbbrevRe.exec(string.substring(i));
+    return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
+  }
+  function d3_time_parseMonth(date, string, i) {
+    d3_time_monthRe.lastIndex = 0;
+    var n = d3_time_monthRe.exec(string.substring(i));
+    return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
+  }
+  function d3_time_parseLocaleFull(date, string, i) {
+    return d3_time_parse(date, d3_time_formats.c.toString(), string, i);
+  }
+  function d3_time_parseLocaleDate(date, string, i) {
+    return d3_time_parse(date, d3_time_formats.x.toString(), string, i);
+  }
+  function d3_time_parseLocaleTime(date, string, i) {
+    return d3_time_parse(date, d3_time_formats.X.toString(), string, i);
+  }
+  function d3_time_parseFullYear(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.substring(i, i + 4));
+    return n ? (date.y = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseYear(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.substring(i, i + 2));
+    return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1;
+  }
+  function d3_time_expandYear(d) {
+    return d + (d > 68 ? 1900 : 2e3);
+  }
+  function d3_time_parseMonthNumber(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.substring(i, i + 2));
+    return n ? (date.m = n[0] - 1, i + n[0].length) : -1;
+  }
+  function d3_time_parseDay(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.substring(i, i + 2));
+    return n ? (date.d = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseDayOfYear(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.substring(i, i + 3));
+    return n ? (date.j = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseHour24(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.substring(i, i + 2));
+    return n ? (date.H = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseMinutes(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.substring(i, i + 2));
+    return n ? (date.M = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseSeconds(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.substring(i, i + 2));
+    return n ? (date.S = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseMilliseconds(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.substring(i, i + 3));
+    return n ? (date.L = +n[0], i + n[0].length) : -1;
+  }
+  var d3_time_numberRe = /^\s*\d+/;
+  function d3_time_parseAmPm(date, string, i) {
+    var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase());
+    return n == null ? -1 : (date.p = n, i);
+  }
+  var d3_time_amPmLookup = d3.map({
+    am: 0,
+    pm: 1
+  });
+  function d3_time_zone(d) {
+    var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = ~~(Math.abs(z) / 60), zm = Math.abs(z) % 60;
+    return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2);
+  }
+  function d3_time_parseLiteralPercent(date, string, i) {
+    d3_time_percentRe.lastIndex = 0;
+    var n = d3_time_percentRe.exec(string.substring(i, i + 1));
+    return n ? i + n[0].length : -1;
+  }
+  d3.time.format.utc = function(template) {
+    var local = d3.time.format(template);
+    function format(date) {
+      try {
+        d3_time = d3_time_utc;
+        var utc = new d3_time();
+        utc._ = date;
+        return local(utc);
+      } finally {
+        d3_time = Date;
+      }
+    }
+    format.parse = function(string) {
+      try {
+        d3_time = d3_time_utc;
+        var date = local.parse(string);
+        return date && date._;
+      } finally {
+        d3_time = Date;
+      }
+    };
+    format.toString = local.toString;
+    return format;
+  };
+  var d3_time_formatIso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");
+  d3.time.format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso;
+  function d3_time_formatIsoNative(date) {
+    return date.toISOString();
+  }
+  d3_time_formatIsoNative.parse = function(string) {
+    var date = new Date(string);
+    return isNaN(date) ? null : date;
+  };
+  d3_time_formatIsoNative.toString = d3_time_formatIso.toString;
+  d3.time.second = d3_time_interval(function(date) {
+    return new d3_time(Math.floor(date / 1e3) * 1e3);
+  }, function(date, offset) {
+    date.setTime(date.getTime() + Math.floor(offset) * 1e3);
+  }, function(date) {
+    return date.getSeconds();
+  });
+  d3.time.seconds = d3.time.second.range;
+  d3.time.seconds.utc = d3.time.second.utc.range;
+  d3.time.minute = d3_time_interval(function(date) {
+    return new d3_time(Math.floor(date / 6e4) * 6e4);
+  }, function(date, offset) {
+    date.setTime(date.getTime() + Math.floor(offset) * 6e4);
+  }, function(date) {
+    return date.getMinutes();
+  });
+  d3.time.minutes = d3.time.minute.range;
+  d3.time.minutes.utc = d3.time.minute.utc.range;
+  d3.time.hour = d3_time_interval(function(date) {
+    var timezone = date.getTimezoneOffset() / 60;
+    return new d3_time((Math.floor(date / 36e5 - timezone) + timezone) * 36e5);
+  }, function(date, offset) {
+    date.setTime(date.getTime() + Math.floor(offset) * 36e5);
+  }, function(date) {
+    return date.getHours();
+  });
+  d3.time.hours = d3.time.hour.range;
+  d3.time.hours.utc = d3.time.hour.utc.range;
+  d3.time.month = d3_time_interval(function(date) {
+    date = d3.time.day(date);
+    date.setDate(1);
+    return date;
+  }, function(date, offset) {
+    date.setMonth(date.getMonth() + offset);
+  }, function(date) {
+    return date.getMonth();
+  });
+  d3.time.months = d3.time.month.range;
+  d3.time.months.utc = d3.time.month.utc.range;
+  function d3_time_scale(linear, methods, format) {
+    function scale(x) {
+      return linear(x);
+    }
+    scale.invert = function(x) {
+      return d3_time_scaleDate(linear.invert(x));
+    };
+    scale.domain = function(x) {
+      if (!arguments.length) return linear.domain().map(d3_time_scaleDate);
+      linear.domain(x);
+      return scale;
+    };
+    scale.nice = function(m) {
+      return scale.domain(d3_scale_nice(scale.domain(), m));
+    };
+    scale.ticks = function(m, k) {
+      var extent = d3_scaleExtent(scale.domain());
+      if (typeof m !== "function") {
+        var span = extent[1] - extent[0], target = span / m, i = d3.bisect(d3_time_scaleSteps, target);
+        if (i == d3_time_scaleSteps.length) return methods.year(extent, m);
+        if (!i) return linear.ticks(m).map(d3_time_scaleDate);
+        if (target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target) --i;
+        m = methods[i];
+        k = m[1];
+        m = m[0].range;
+      }
+      return m(extent[0], new Date(+extent[1] + 1), k);
+    };
+    scale.tickFormat = function() {
+      return format;
+    };
+    scale.copy = function() {
+      return d3_time_scale(linear.copy(), methods, format);
+    };
+    return d3_scale_linearRebind(scale, linear);
+  }
+  function d3_time_scaleDate(t) {
+    return new Date(t);
+  }
+  function d3_time_scaleFormat(formats) {
+    return function(date) {
+      var i = formats.length - 1, f = formats[i];
+      while (!f[1](date)) f = formats[--i];
+      return f[0](date);
+    };
+  }
+  function d3_time_scaleSetYear(y) {
+    var d = new Date(y, 0, 1);
+    d.setFullYear(y);
+    return d;
+  }
+  function d3_time_scaleGetYear(d) {
+    var y = d.getFullYear(), d0 = d3_time_scaleSetYear(y), d1 = d3_time_scaleSetYear(y + 1);
+    return y + (d - d0) / (d1 - d0);
+  }
+  var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ];
+  var d3_time_scaleLocalMethods = [ [ d3.time.second, 1 ], [ d3.time.second, 5 ], [ d3.time.second, 15 ], [ d3.time.second, 30 ], [ d3.time.minute, 1 ], [ d3.time.minute, 5 ], [ d3.time.minute, 15 ], [ d3.time.minute, 30 ], [ d3.time.hour, 1 ], [ d3.time.hour, 3 ], [ d3.time.hour, 6 ], [ d3.time.hour, 12 ], [ d3.time.day, 1 ], [ d3.time.day, 2 ], [ d3.time.week, 1 ], [ d3.time.month, 1 ], [ d3.time.month, 3 ], [ d3.time.year, 1 ] ];
+  var d3_time_scaleLocalFormats = [ [ d3.time.format("%Y"), d3_true ], [ d3.time.format("%B"), function(d) {
+    return d.getMonth();
+  } ], [ d3.time.format("%b %d"), function(d) {
+    return d.getDate() != 1;
+  } ], [ d3.time.format("%a %d"), function(d) {
+    return d.getDay() && d.getDate() != 1;
+  } ], [ d3.time.format("%I %p"), function(d) {
+    return d.getHours();
+  } ], [ d3.time.format("%I:%M"), function(d) {
+    return d.getMinutes();
+  } ], [ d3.time.format(":%S"), function(d) {
+    return d.getSeconds();
+  } ], [ d3.time.format(".%L"), function(d) {
+    return d.getMilliseconds();
+  } ] ];
+  var d3_time_scaleLinear = d3.scale.linear(), d3_time_scaleLocalFormat = d3_time_scaleFormat(d3_time_scaleLocalFormats);
+  d3_time_scaleLocalMethods.year = function(extent, m) {
+    return d3_time_scaleLinear.domain(extent.map(d3_time_scaleGetYear)).ticks(m).map(d3_time_scaleSetYear);
+  };
+  d3.time.scale = function() {
+    return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat);
+  };
+  var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) {
+    return [ m[0].utc, m[1] ];
+  });
+  var d3_time_scaleUTCFormats = [ [ d3.time.format.utc("%Y"), d3_true ], [ d3.time.format.utc("%B"), function(d) {
+    return d.getUTCMonth();
+  } ], [ d3.time.format.utc("%b %d"), function(d) {
+    return d.getUTCDate() != 1;
+  } ], [ d3.time.format.utc("%a %d"), function(d) {
+    return d.getUTCDay() && d.getUTCDate() != 1;
+  } ], [ d3.time.format.utc("%I %p"), function(d) {
+    return d.getUTCHours();
+  } ], [ d3.time.format.utc("%I:%M"), function(d) {
+    return d.getUTCMinutes();
+  } ], [ d3.time.format.utc(":%S"), function(d) {
+    return d.getUTCSeconds();
+  } ], [ d3.time.format.utc(".%L"), function(d) {
+    return d.getUTCMilliseconds();
+  } ] ];
+  var d3_time_scaleUTCFormat = d3_time_scaleFormat(d3_time_scaleUTCFormats);
+  function d3_time_scaleUTCSetYear(y) {
+    var d = new Date(Date.UTC(y, 0, 1));
+    d.setUTCFullYear(y);
+    return d;
+  }
+  function d3_time_scaleUTCGetYear(d) {
+    var y = d.getUTCFullYear(), d0 = d3_time_scaleUTCSetYear(y), d1 = d3_time_scaleUTCSetYear(y + 1);
+    return y + (d - d0) / (d1 - d0);
+  }
+  d3_time_scaleUTCMethods.year = function(extent, m) {
+    return d3_time_scaleLinear.domain(extent.map(d3_time_scaleUTCGetYear)).ticks(m).map(d3_time_scaleUTCSetYear);
+  };
+  d3.time.scale.utc = function() {
+    return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat);
+  };
+  d3.text = d3_xhrType(function(request) {
+    return request.responseText;
+  });
+  d3.json = function(url, callback) {
+    return d3_xhr(url, "application/json", d3_json, callback);
+  };
+  function d3_json(request) {
+    return JSON.parse(request.responseText);
+  }
+  d3.html = function(url, callback) {
+    return d3_xhr(url, "text/html", d3_html, callback);
+  };
+  function d3_html(request) {
+    var range = d3_document.createRange();
+    range.selectNode(d3_document.body);
+    return range.createContextualFragment(request.responseText);
+  }
+  d3.xml = d3_xhrType(function(request) {
+    return request.responseXML;
+  });
+  return d3;
+}();
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/d3/d3.v3.min.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/d3/d3.v3.min.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/d3/d3.v3.min.js	(revision 2119)
@@ -0,0 +1,5 @@
+d3=function(){function n(n){return null!=n&&!isNaN(n)}function t(n){return n.length}function e(n){for(var t=1;n*t%1;)t*=10;return t}function r(n,t){try{for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}catch(r){n.prototype=t}}function i(){}function u(){}function a(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function o(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.substring(1);for(var e=0,r=Ca.length;r>e;++e){var i=Ca[e]+t;if(i in n)return i}}function c(n){for(var t=-1,e=n.length,r=[];++t<e;)r.push(n[t]);return r}function l(n){return Array.prototype.slice.call(n)}function s(){}function f(){}function h(n){function t(){for(var t,r=e,i=-1,u=r.length;++i<u;)(t=r[i].on)&&t.apply(this,arguments);return n}var e=[],r=new i;return t.on=function(t,i){var u,a=r.get(t);return arguments.length<2?a&&a.on:(a&&(a.on=null,e=e.slice(0,u=e.indexOf(a)).concat(e.slice(u+1)),r.remove(t)),i&&e.push(r.set(t,{on:i})),n)},t}function g(){ya.event.preventDefault()}function p(){for(var n,t=ya.event;n=t.sourceEvent;)t=n;return t}function m(n){for(var t=new f,e=0,r=arguments.length;++e<r;)t[arguments[e]]=h(t);return t.of=function(e,r){return function(i){try{var u=i.sourceEvent=ya.event;i.target=n,ya.event=i,t[i.type].apply(e,r)}finally{ya.event=u}}},t}function d(n){return La(n,Ya),n}function v(n){return"function"==typeof n?n:function(){return Ha(n,this)}}function y(n){return"function"==typeof n?n:function(){return Fa(n,this)}}function M(n,t){function e(){this.removeAttribute(n)}function r(){this.removeAttributeNS(n.space,n.local)}function i(){this.setAttribute(n,t)}function u(){this.setAttributeNS(n.space,n.local,t)}function a(){var e=t.apply(this,arguments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function o(){var e=t.apply(this,arguments);null==e?this.removeAttributeNS(n.space,n.local):this.setAttributeNS(n.space,n.local,e)}return n=ya.ns.qualify(n),null==t?n.local?r:e:"function"==typeof t?n.local?o:a:n.local?u:i}function x(n){return n.trim().replace(/\s+/g," ")}function b(n){return new RegExp("(?:^|\\s+)"+ya.requote(n)+"(?:\\s+|$)","g")}function _(n,t){function e(){for(var e=-1;++e<i;)n[e](this,t)}function r(){for(var e=-1,r=t.apply(this,arguments);++e<i;)n[e](this,r)}n=n.trim().split(/\s+/).map(w);var i=n.length;return"function"==typeof t?r:e}function w(n){var t=b(n);return function(e,r){if(i=e.classList)return r?i.add(n):i.remove(n);var i=e.getAttribute("class")||"";r?(t.lastIndex=0,t.test(i)||e.setAttribute("class",x(i+" "+n))):e.setAttribute("class",x(i.replace(t," ")))}}function S(n,t,e){function r(){this.style.removeProperty(n)}function i(){this.style.setProperty(n,t,e)}function u(){var r=t.apply(this,arguments);null==r?this.style.removeProperty(n):this.style.setProperty(n,r,e)}return null==t?r:"function"==typeof t?u:i}function E(n,t){function e(){delete this[n]}function r(){this[n]=t}function i(){var e=t.apply(this,arguments);null==e?delete this[n]:this[n]=e}return null==t?e:"function"==typeof t?i:r}function k(n){return"function"==typeof n?n:(n=ya.ns.qualify(n)).local?function(){return Ma.createElementNS(n.space,n.local)}:function(){return Ma.createElementNS(this.namespaceURI,n)}}function A(n){return{__data__:n}}function N(n){return function(){return Oa(this,n)}}function q(n){return arguments.length||(n=ya.ascending),function(t,e){return!t-!e||n(t.__data__,e.__data__)}}function T(n,t){for(var e=0,r=n.length;r>e;e++)for(var i,u=n[e],a=0,o=u.length;o>a;a++)(i=u[a])&&t(i,a,e);return n}function C(n){return La(n,Ua),n}function z(n){var t,e;return function(r,i,u){var a,o=n[u].update,c=o.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(a=o[t])&&++t<c;);return a}}function D(n,t,e){function r(){var t=this[a];t&&(this.removeEventListener(n,t,t.$),delete this[a])}function i(){var i=c(t,za(arguments));r.call(this),this.addEventListener(n,this[a]=i,i.$=e),i._=t}function u(){var t,e=new RegExp("^__on([^.]+)"+ya.requote(n)+"$");for(var r in this)if(t=r.match(e)){var i=this[r];this.removeEventListener(t[1],i,i.$),delete this[r]}}var a="__on"+n,o=n.indexOf("."),c=j;o>0&&(n=n.substring(0,o));var l=Va.get(n);return l&&(n=l,c=L),o?t?i:r:t?s:u}function j(n,t){return function(e){var r=ya.event;ya.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ya.event=r}}}function L(n,t){var e=j(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function H(){var n=".dragsuppress-"+ ++Za,t="touchmove"+n,e="selectstart"+n,r="dragstart"+n,i="click"+n,u=ya.select(ba).on(t,g).on(e,g).on(r,g),a=xa.style,o=a[Xa];return a[Xa]="none",function(t){function e(){u.on(i,null)}u.on(n,null),a[Xa]=o,t&&(u.on(i,function(){g(),e()},!0),setTimeout(e,0))}}function F(n,t){var e=n.ownerSVGElement||n;if(e.createSVGPoint){var r=e.createSVGPoint();if(0>Ba&&(ba.scrollX||ba.scrollY)){e=ya.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var i=e[0][0].getScreenCTM();Ba=!(i.f||i.e),e.remove()}return Ba?(r.x=t.pageX,r.y=t.pageY):(r.x=t.clientX,r.y=t.clientY),r=r.matrixTransform(n.getScreenCTM().inverse()),[r.x,r.y]}var u=n.getBoundingClientRect();return[t.clientX-u.left-n.clientLeft,t.clientY-u.top-n.clientTop]}function P(){}function O(n,t,e){return new Y(n,t,e)}function Y(n,t,e){this.h=n,this.s=t,this.l=e}function R(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(a-u)*n/60:180>n?a:240>n?u+(a-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,a;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,a=.5>=e?e*(1+t):e+t-e*t,u=2*e-a,at(i(n+120),i(n),i(n-120))}function U(n){return n>0?1:0>n?-1:0}function I(n){return n>1?0:-1>n?Ka:Math.acos(n)}function V(n){return n>1?Ka/2:-1>n?-Ka/2:Math.asin(n)}function X(n){return(Math.exp(n)-Math.exp(-n))/2}function Z(n){return(Math.exp(n)+Math.exp(-n))/2}function B(n){return(n=Math.sin(n/2))*n}function $(n,t,e){return new W(n,t,e)}function W(n,t,e){this.h=n,this.c=t,this.l=e}function J(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),G(e,Math.cos(n*=to)*t,Math.sin(n)*t)}function G(n,t,e){return new K(n,t,e)}function K(n,t,e){this.l=n,this.a=t,this.b=e}function Q(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=tt(i)*uo,r=tt(r)*ao,u=tt(u)*oo,at(rt(3.2404542*i-1.5371385*r-.4985314*u),rt(-.969266*i+1.8760108*r+.041556*u),rt(.0556434*i-.2040259*r+1.0572252*u))}function nt(n,t,e){return n>0?$(Math.atan2(e,t)*eo,Math.sqrt(t*t+e*e),n):$(0/0,0/0,n)}function tt(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function et(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function rt(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function it(n){return at(n>>16,255&n>>8,255&n)}function ut(n){return it(n)+""}function at(n,t,e){return new ot(n,t,e)}function ot(n,t,e){this.r=n,this.g=t,this.b=e}function ct(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function lt(n,t,e){var r,i,u,a=0,o=0,c=0;if(r=/([a-z]+)\((.*)\)/i.exec(n))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(gt(i[0]),gt(i[1]),gt(i[2]))}return(u=so.get(n))?t(u.r,u.g,u.b):(null!=n&&"#"===n.charAt(0)&&(4===n.length?(a=n.charAt(1),a+=a,o=n.charAt(2),o+=o,c=n.charAt(3),c+=c):7===n.length&&(a=n.substring(1,3),o=n.substring(3,5),c=n.substring(5,7)),a=parseInt(a,16),o=parseInt(o,16),c=parseInt(c,16)),t(a,o,c))}function st(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),a=Math.max(n,t,e),o=a-u,c=(a+u)/2;return o?(i=.5>c?o/(a+u):o/(2-a-u),r=n==a?(t-e)/o+(e>t?6:0):t==a?(e-n)/o+2:(n-t)/o+4,r*=60):(r=0/0,i=c>0&&1>c?0:r),O(r,i,c)}function ft(n,t,e){n=ht(n),t=ht(t),e=ht(e);var r=et((.4124564*n+.3575761*t+.1804375*e)/uo),i=et((.2126729*n+.7151522*t+.072175*e)/ao),u=et((.0193339*n+.119192*t+.9503041*e)/oo);return G(116*i-16,500*(r-i),200*(i-u))}function ht(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function gt(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function pt(n){return"function"==typeof n?n:function(){return n}}function mt(n){return n}function dt(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),vt(t,e,n,r)}}function vt(n,t,e,r){function i(){var n,t=c.status;if(!t&&c.responseText||t>=200&&300>t||304===t){try{n=e.call(u,c)}catch(r){return a.error.call(u,r),void 0}a.load.call(u,n)}else a.error.call(u,c)}var u={},a=ya.dispatch("progress","load","error"),o={},c=new XMLHttpRequest,l=null;return!ba.XDomainRequest||"withCredentials"in c||!/^(http(s)?:)?\/\//.test(n)||(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=i:c.onreadystatechange=function(){c.readyState>3&&i()},c.onprogress=function(n){var t=ya.event;ya.event=n;try{a.progress.call(u,c)}finally{ya.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?o[n]:(null==t?delete o[n]:o[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(l=n,u):l},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(za(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),c.open(e,n,!0),null==t||"accept"in o||(o.accept=t+",*/*"),c.setRequestHeader)for(var a in o)c.setRequestHeader(a,o[a]);return null!=t&&c.overrideMimeType&&c.overrideMimeType(t),null!=l&&(c.responseType=l),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),c.send(null==r?null:r),u},u.abort=function(){return c.abort(),u},ya.rebind(u,a,"on"),null==r?u:u.get(yt(r))}function yt(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Mt(){var n=bt(),t=_t()-n;t>24?(isFinite(t)&&(clearTimeout(po),po=setTimeout(Mt,t)),go=0):(go=1,vo(Mt))}function xt(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now()),mo.callback=n,mo.time=e+t}function bt(){var n=Date.now();for(mo=fo;mo;)n>=mo.time&&(mo.flush=mo.callback(n-mo.time)),mo=mo.next;return n}function _t(){for(var n,t=fo,e=1/0;t;)t.flush?t=n?n.next=t.next:fo=t.next:(t.time<e&&(e=t.time),t=(n=t).next);return ho=n,e}function wt(n,t){var e=Math.pow(10,3*Math.abs(8-t));return{scale:t>8?function(n){return n/e}:function(n){return n*e},symbol:n}}function St(n,t){return t-(n?Math.ceil(Math.log(n)/Math.LN10):1)}function Et(n){return n+""}function kt(){}function At(n,t,e){var r=e.s=n+t,i=r-n,u=r-i;e.t=n-u+(t-i)}function Nt(n,t){n&&qo.hasOwnProperty(n.type)&&qo[n.type](n,t)}function qt(n,t,e){var r,i=-1,u=n.length-e;for(t.lineStart();++i<u;)r=n[i],t.point(r[0],r[1]);t.lineEnd()}function Tt(n,t){var e=-1,r=n.length;for(t.polygonStart();++e<r;)qt(n[e],t,1);t.polygonEnd()}function Ct(){function n(n,t){n*=to,t=t*to/2+Ka/4;var e=n-r,a=Math.cos(t),o=Math.sin(t),c=u*o,l=i*a+c*Math.cos(e),s=c*Math.sin(e);Co.add(Math.atan2(s,l)),r=n,i=a,u=o}var t,e,r,i,u;zo.point=function(a,o){zo.point=n,r=(t=a)*to,i=Math.cos(o=(e=o)*to/2+Ka/4),u=Math.sin(o)},zo.lineEnd=function(){n(t,e)}}function zt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function Dt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function jt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Lt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function Ht(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function Ft(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function Pt(n){return[Math.atan2(n[1],n[0]),V(n[2])]}function Ot(n,t){return Math.abs(n[0]-t[0])<Qa&&Math.abs(n[1]-t[1])<Qa}function Yt(n,t){n*=to;var e=Math.cos(t*=to);Rt(e*Math.cos(n),e*Math.sin(n),Math.sin(t))}function Rt(n,t,e){++Do,Lo+=(n-Lo)/Do,Ho+=(t-Ho)/Do,Fo+=(e-Fo)/Do}function Ut(){function n(n,i){n*=to;var u=Math.cos(i*=to),a=u*Math.cos(n),o=u*Math.sin(n),c=Math.sin(i),l=Math.atan2(Math.sqrt((l=e*c-r*o)*l+(l=r*a-t*c)*l+(l=t*o-e*a)*l),t*a+e*o+r*c);jo+=l,Po+=l*(t+(t=a)),Oo+=l*(e+(e=o)),Yo+=l*(r+(r=c)),Rt(t,e,r)}var t,e,r;Vo.point=function(i,u){i*=to;var a=Math.cos(u*=to);t=a*Math.cos(i),e=a*Math.sin(i),r=Math.sin(u),Vo.point=n,Rt(t,e,r)}}function It(){Vo.point=Yt}function Vt(){function n(n,t){n*=to;var e=Math.cos(t*=to),a=e*Math.cos(n),o=e*Math.sin(n),c=Math.sin(t),l=i*c-u*o,s=u*a-r*c,f=r*o-i*a,h=Math.sqrt(l*l+s*s+f*f),g=r*a+i*o+u*c,p=h&&-I(g)/h,m=Math.atan2(h,g);Ro+=p*l,Uo+=p*s,Io+=p*f,jo+=m,Po+=m*(r+(r=a)),Oo+=m*(i+(i=o)),Yo+=m*(u+(u=c)),Rt(r,i,u)}var t,e,r,i,u;Vo.point=function(a,o){t=a,e=o,Vo.point=n,a*=to;var c=Math.cos(o*=to);r=c*Math.cos(a),i=c*Math.sin(a),u=Math.sin(o),Rt(r,i,u)},Vo.lineEnd=function(){n(t,e),Vo.lineEnd=It,Vo.point=Yt}}function Xt(){return!0}function Zt(n,t,e,r,i){var u=[],a=[];if(n.forEach(function(n){if(!((t=n.length-1)<=0)){var t,e=n[0],r=n[t];if(Ot(e,r)){i.lineStart();for(var o=0;t>o;++o)i.point((e=n[o])[0],e[1]);return i.lineEnd(),void 0}var c={point:e,points:n,other:null,visited:!1,entry:!0,subject:!0},l={point:e,points:[e],other:c,visited:!1,entry:!1,subject:!1};c.other=l,u.push(c),a.push(l),c={point:r,points:[r],other:null,visited:!1,entry:!1,subject:!0},l={point:r,points:[r],other:c,visited:!1,entry:!0,subject:!1},c.other=l,u.push(c),a.push(l)}}),a.sort(t),Bt(u),Bt(a),u.length){if(e)for(var o=1,c=!e(a[0].point),l=a.length;l>o;++o)a[o].entry=c=!c;for(var s,f,h,g=u[0];;){for(s=g;s.visited;)if((s=s.next)===g)return;f=s.points,i.lineStart();do{if(s.visited=s.other.visited=!0,s.entry){if(s.subject)for(var o=0;o<f.length;o++)i.point((h=f[o])[0],h[1]);else r(s.point,s.next.point,1,i);s=s.next}else{if(s.subject){f=s.prev.points;for(var o=f.length;--o>=0;)i.point((h=f[o])[0],h[1])}else r(s.point,s.prev.point,-1,i);s=s.prev}s=s.other,f=s.points}while(!s.visited);i.lineEnd()}}}function Bt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r<t;)i.next=e=n[r],e.prev=i,i=e;i.next=e=n[0],e.prev=i}}function $t(n,t,e,r){return function(i){function u(t,e){n(t,e)&&i.point(t,e)}function a(n,t){m.point(n,t)}function o(){d.point=a,m.lineStart()}function c(){d.point=u,m.lineEnd()}function l(n,t){y.point(n,t),p.push([n,t])}function s(){y.lineStart(),p=[]}function f(){l(p[0][0],p[0][1]),y.lineEnd();var n,t=y.clean(),e=v.buffer(),r=e.length;if(p.pop(),g.push(p),p=null,r){if(1&t){n=e[0];var u,r=n.length-1,a=-1;for(i.lineStart();++a<r;)i.point((u=n[a])[0],u[1]);return i.lineEnd(),void 0}r>1&&2&t&&e.push(e.pop().concat(e.shift())),h.push(e.filter(Wt))}}var h,g,p,m=t(i),d={point:u,lineStart:o,lineEnd:c,polygonStart:function(){d.point=l,d.lineStart=s,d.lineEnd=f,h=[],g=[],i.polygonStart()},polygonEnd:function(){d.point=u,d.lineStart=o,d.lineEnd=c,h=ya.merge(h),h.length?Zt(h,Gt,null,e,i):r(g)&&(i.lineStart(),e(null,null,1,i),i.lineEnd()),i.polygonEnd(),h=g=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},v=Jt(),y=t(v);return d}}function Wt(n){return n.length>1}function Jt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:s,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Gt(n,t){return((n=n.point)[0]<0?n[1]-Ka/2-Qa:Ka/2-n[1])-((t=t.point)[0]<0?t[1]-Ka/2-Qa:Ka/2-t[1])}function Kt(n,t){var e=n[0],r=n[1],i=[Math.sin(e),-Math.cos(e),0],u=0,a=!1,o=!1,c=0;Co.reset();for(var l=0,s=t.length;s>l;++l){var f=t[l],h=f.length;if(h){for(var g=f[0],p=g[0],m=g[1]/2+Ka/4,d=Math.sin(m),v=Math.cos(m),y=1;;){y===h&&(y=0),n=f[y];var M=n[0],x=n[1]/2+Ka/4,b=Math.sin(x),_=Math.cos(x),w=M-p,S=Math.abs(w)>Ka,E=d*b;if(Co.add(Math.atan2(E*Math.sin(w),v*_+E*Math.cos(w))),Math.abs(x)<Qa&&(o=!0),u+=S?w+(w>=0?2:-2)*Ka:w,S^p>=e^M>=e){var k=jt(zt(g),zt(n));Ft(k);var A=jt(i,k);Ft(A);var N=(S^w>=0?-1:1)*V(A[2]);r>N&&(c+=S^w>=0?1:-1)}if(!y++)break;p=M,d=b,v=_,g=n}Math.abs(u)>Qa&&(a=!0)}}return(!o&&!a&&0>Co||-Qa>u)^1&c}function Qt(n){var t,e=0/0,r=0/0,i=0/0;return{lineStart:function(){n.lineStart(),t=1},point:function(u,a){var o=u>0?Ka:-Ka,c=Math.abs(u-e);Math.abs(c-Ka)<Qa?(n.point(e,r=(r+a)/2>0?Ka/2:-Ka/2),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(o,r),n.point(u,r),t=0):i!==o&&c>=Ka&&(Math.abs(e-i)<Qa&&(e-=i*Qa),Math.abs(u-o)<Qa&&(u-=o*Qa),r=ne(e,r,u,a),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(o,r),t=0),n.point(e=u,r=a),i=o},lineEnd:function(){n.lineEnd(),e=r=0/0},clean:function(){return 2-t}}}function ne(n,t,e,r){var i,u,a=Math.sin(n-e);return Math.abs(a)>Qa?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*a)):(t+r)/2}function te(n,t,e,r){var i;if(null==n)i=e*Ka/2,r.point(-Ka,i),r.point(0,i),r.point(Ka,i),r.point(Ka,0),r.point(Ka,-i),r.point(0,-i),r.point(-Ka,-i),r.point(-Ka,0),r.point(-Ka,i);else if(Math.abs(n[0]-t[0])>Qa){var u=(n[0]<t[0]?1:-1)*Ka;i=e*u/2,r.point(-u,i),r.point(0,i),r.point(u,i)}else r.point(t[0],t[1])}function ee(n){return Kt(Zo,n)}function re(n){function t(n,t){return Math.cos(n)*Math.cos(t)>a}function e(n){var e,u,a,c,s;return{lineStart:function(){c=a=!1,s=1},point:function(f,h){var g,p=[f,h],m=t(f,h),d=o?m?0:i(f,h):m?i(f+(0>f?Ka:-Ka),h):0;if(!e&&(c=a=m)&&n.lineStart(),m!==a&&(g=r(e,p),(Ot(e,g)||Ot(p,g))&&(p[0]+=Qa,p[1]+=Qa,m=t(p[0],p[1]))),m!==a)s=0,m?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(l&&e&&o^m){var v;d&u||!(v=r(p,e,!0))||(s=0,o?(n.lineStart(),n.point(v[0][0],v[0][1]),n.point(v[1][0],v[1][1]),n.lineEnd()):(n.point(v[1][0],v[1][1]),n.lineEnd(),n.lineStart(),n.point(v[0][0],v[0][1])))}!m||e&&Ot(e,p)||n.point(p[0],p[1]),e=p,a=m,u=d},lineEnd:function(){a&&n.lineEnd(),e=null},clean:function(){return s|(c&&a)<<1}}}function r(n,t,e){var r=zt(n),i=zt(t),u=[1,0,0],o=jt(r,i),c=Dt(o,o),l=o[0],s=c-l*l;if(!s)return!e&&n;var f=a*c/s,h=-a*l/s,g=jt(u,o),p=Ht(u,f),m=Ht(o,h);Lt(p,m);var d=g,v=Dt(p,d),y=Dt(d,d),M=v*v-y*(Dt(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=Ht(d,(-v-x)/y);if(Lt(b,p),b=Pt(b),!e)return b;var _,w=n[0],S=t[0],E=n[1],k=t[1];w>S&&(_=w,w=S,S=_);var A=S-w,N=Math.abs(A-Ka)<Qa,q=N||Qa>A;if(!N&&E>k&&(_=E,E=k,k=_),q?N?E+k>0^b[1]<(Math.abs(b[0]-w)<Qa?E:k):E<=b[1]&&b[1]<=k:A>Ka^(w<=b[0]&&b[0]<=S)){var T=Ht(d,(-v+x)/y);return Lt(T,p),[b,Pt(T)]}}}function i(t,e){var r=o?n:Ka-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}function u(n){return Kt(c,n)}var a=Math.cos(n),o=a>0,c=[n,0],l=Math.abs(a)>Qa,s=Ne(n,6*to);return $t(t,e,s,u)}function ie(n,t,e,r){function i(r,i){return Math.abs(r[0]-n)<Qa?i>0?0:3:Math.abs(r[0]-e)<Qa?i>0?2:1:Math.abs(r[1]-t)<Qa?i>0?1:0:i>0?3:2}function u(n,t){return a(n.point,t.point)}function a(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}function o(i,u){var a=u[0]-i[0],o=u[1]-i[1],c=[0,1];return Math.abs(a)<Qa&&Math.abs(o)<Qa?n<=i[0]&&i[0]<=e&&t<=i[1]&&i[1]<=r:ue(n-i[0],a,c)&&ue(i[0]-e,-a,c)&&ue(t-i[1],o,c)&&ue(i[1]-r,-o,c)?(c[1]<1&&(u[0]=i[0]+c[1]*a,u[1]=i[1]+c[1]*o),c[0]>0&&(i[0]+=c[0]*a,i[1]+=c[0]*o),!0):!1}return function(c){function l(u){var a=i(u,-1),o=s([0===a||3===a?n:e,a>1?r:t]);return o}function s(n){for(var t=0,e=M.length,r=n[1],i=0;e>i;++i)for(var u,a=1,o=M[i],c=o.length,l=o[0];c>a;++a)u=o[a],l[1]<=r?u[1]>r&&f(l,u,n)>0&&++t:u[1]<=r&&f(l,u,n)<0&&--t,l=u;return 0!==t}function f(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(e[0]-n[0])*(t[1]-n[1])}function h(u,o,c,l){var s=0,f=0;if(null==u||(s=i(u,c))!==(f=i(o,c))||a(u,o)<0^c>0){do l.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+c+4)%4)!==f)}else l.point(o[0],o[1])}function g(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function p(n,t){g(n,t)&&c.point(n,t)}function m(){T.point=v,M&&M.push(x=[]),A=!0,k=!1,S=E=0/0}function d(){y&&(v(b,_),w&&k&&q.rejoin(),y.push(q.buffer())),T.point=p,k&&c.lineEnd()}function v(n,t){n=Math.max(-Bo,Math.min(Bo,n)),t=Math.max(-Bo,Math.min(Bo,t));var e=g(n,t);if(M&&x.push([n,t]),A)b=n,_=t,w=e,A=!1,e&&(c.lineStart(),c.point(n,t));else if(e&&k)c.point(n,t);else{var r=[S,E],i=[n,t];o(r,i)?(k||(c.lineStart(),c.point(r[0],r[1])),c.point(i[0],i[1]),e||c.lineEnd()):e&&(c.lineStart(),c.point(n,t))}S=n,E=t,k=e}var y,M,x,b,_,w,S,E,k,A,N=c,q=Jt(),T={point:p,lineStart:m,lineEnd:d,polygonStart:function(){c=q,y=[],M=[]},polygonEnd:function(){c=N,(y=ya.merge(y)).length?(c.polygonStart(),Zt(y,u,l,h,c),c.polygonEnd()):s([n,t])&&(c.polygonStart(),c.lineStart(),h(null,null,1,c),c.lineEnd(),c.polygonEnd()),y=M=x=null}};return T}}function ue(n,t,e){if(Math.abs(t)<Qa)return 0>=n;var r=n/t;if(t>0){if(r>e[1])return!1;r>e[0]&&(e[0]=r)}else{if(r<e[0])return!1;r<e[1]&&(e[1]=r)}return!0}function ae(n,t){function e(e,r){return e=n(e,r),t(e[0],e[1])}return n.invert&&t.invert&&(e.invert=function(e,r){return e=t.invert(e,r),e&&n.invert(e[0],e[1])}),e}function oe(n){var t=0,e=Ka/3,r=be(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Ka/180,e=n[1]*Ka/180):[180*(t/Ka),180*(e/Ka)]},i}function ce(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),a-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),a=Math.sqrt(u)/i;return e.invert=function(n,t){var e=a-t;return[Math.atan2(n,e)/i,V((u-(n*n+e*e)*i*i)/(2*i))]},e}function le(){function n(n,t){Wo+=i*n-r*t,r=n,i=t}var t,e,r,i;nc.point=function(u,a){nc.point=n,t=r=u,e=i=a},nc.lineEnd=function(){n(t,e)}}function se(n,t){Jo>n&&(Jo=n),n>Ko&&(Ko=n),Go>t&&(Go=t),t>Qo&&(Qo=t)}function fe(){function n(n,t){a.push("M",n,",",t,u)}function t(n,t){a.push("M",n,",",t),o.point=e}function e(n,t){a.push("L",n,",",t)}function r(){o.point=n}function i(){a.push("Z")}var u=he(4.5),a=[],o={point:n,lineStart:function(){o.point=t},lineEnd:r,polygonStart:function(){o.lineEnd=i},polygonEnd:function(){o.lineEnd=r,o.point=n},pointRadius:function(n){return u=he(n),o},result:function(){if(a.length){var n=a.join("");return a=[],n}}};return o}function he(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function ge(n,t){Lo+=n,Ho+=t,++Fo}function pe(){function n(n,r){var i=n-t,u=r-e,a=Math.sqrt(i*i+u*u);Po+=a*(t+n)/2,Oo+=a*(e+r)/2,Yo+=a,ge(t=n,e=r)}var t,e;ec.point=function(r,i){ec.point=n,ge(t=r,e=i)}}function me(){ec.point=ge}function de(){function n(n,t){var e=n-r,u=t-i,a=Math.sqrt(e*e+u*u);Po+=a*(r+n)/2,Oo+=a*(i+t)/2,Yo+=a,a=i*n-r*t,Ro+=a*(r+n),Uo+=a*(i+t),Io+=3*a,ge(r=n,i=t)}var t,e,r,i;ec.point=function(u,a){ec.point=n,ge(t=r=u,e=i=a)},ec.lineEnd=function(){n(t,e)}}function ve(n){function t(t,e){n.moveTo(t,e),n.arc(t,e,a,0,2*Ka)}function e(t,e){n.moveTo(t,e),o.point=r}function r(t,e){n.lineTo(t,e)}function i(){o.point=t}function u(){n.closePath()}var a=4.5,o={point:t,lineStart:function(){o.point=e},lineEnd:i,polygonStart:function(){o.lineEnd=u},polygonEnd:function(){o.lineEnd=i,o.point=t},pointRadius:function(n){return a=n,o},result:s};return o}function ye(n){function t(t){function r(e,r){e=n(e,r),t.point(e[0],e[1])}function i(){M=0/0,S.point=a,t.lineStart()}function a(r,i){var a=zt([r,i]),o=n(r,i);e(M,x,y,b,_,w,M=o[0],x=o[1],y=r,b=a[0],_=a[1],w=a[2],u,t),t.point(M,x)}function o(){S.point=r,t.lineEnd()}function c(){i(),S.point=l,S.lineEnd=s}function l(n,t){a(f=n,h=t),g=M,p=x,m=b,d=_,v=w,S.point=a}function s(){e(M,x,y,b,_,w,g,p,f,m,d,v,u,t),S.lineEnd=o,o()}var f,h,g,p,m,d,v,y,M,x,b,_,w,S={point:r,lineStart:i,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=c},polygonEnd:function(){t.polygonEnd(),S.lineStart=i}};return S}function e(t,u,a,o,c,l,s,f,h,g,p,m,d,v){var y=s-t,M=f-u,x=y*y+M*M;if(x>4*r&&d--){var b=o+g,_=c+p,w=l+m,S=Math.sqrt(b*b+_*_+w*w),E=Math.asin(w/=S),k=Math.abs(Math.abs(w)-1)<Qa?(a+h)/2:Math.atan2(_,b),A=n(k,E),N=A[0],q=A[1],T=N-t,C=q-u,z=M*T-y*C;(z*z/x>r||Math.abs((y*T+M*C)/x-.5)>.3||i>o*g+c*p+l*m)&&(e(t,u,a,o,c,l,N,q,k,b/=S,_/=S,w,d,v),v.point(N,q),e(N,q,k,b,_,w,s,f,h,g,p,m,d,v))}}var r=.5,i=Math.cos(30*to),u=16;return t.precision=function(n){return arguments.length?(u=(r=n*n)>0&&16,t):Math.sqrt(r)},t}function Me(n){var t=ye(function(t,e){return n([t*eo,e*eo])});return function(n){return n=t(n),{point:function(t,e){n.point(t*to,e*to)},sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}}function xe(n){return be(function(){return n})()}function be(n){function t(n){return n=o(n[0]*to,n[1]*to),[n[0]*h+c,l-n[1]*h]}function e(n){return n=o.invert((n[0]-c)/h,(l-n[1])/h),n&&[n[0]*eo,n[1]*eo]}function r(){o=ae(a=Se(v,y,M),u);var n=u(m,d);return c=g-n[0]*h,l=p+n[1]*h,i()}function i(){return s&&(s.valid=!1,s=null),t}var u,a,o,c,l,s,f=ye(function(n,t){return n=u(n,t),[n[0]*h+c,l-n[1]*h]}),h=150,g=480,p=250,m=0,d=0,v=0,y=0,M=0,x=Xo,b=mt,_=null,w=null;return t.stream=function(n){return s&&(s.valid=!1),s=_e(a,x(f(b(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(x=null==n?(_=n,Xo):re((_=+n)*to),i()):_},t.clipExtent=function(n){return arguments.length?(w=n,b=null==n?mt:ie(n[0][0],n[0][1],n[1][0],n[1][1]),i()):w},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(m=n[0]%360*to,d=n[1]%360*to,r()):[m*eo,d*eo]},t.rotate=function(n){return arguments.length?(v=n[0]%360*to,y=n[1]%360*to,M=n.length>2?n[2]%360*to:0,r()):[v*eo,y*eo,M*eo]},ya.rebind(t,f,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function _e(n,t){return{point:function(e,r){r=n(e*to,r*to),e=r[0],t.point(e>Ka?e-2*Ka:-Ka>e?e+2*Ka:e,r[1])},sphere:function(){t.sphere()},lineStart:function(){t.lineStart()},lineEnd:function(){t.lineEnd()},polygonStart:function(){t.polygonStart()},polygonEnd:function(){t.polygonEnd()}}}function we(n,t){return[n,t]}function Se(n,t,e){return n?t||e?ae(ke(n),Ae(t,e)):ke(n):t||e?Ae(t,e):we}function Ee(n){return function(t,e){return t+=n,[t>Ka?t-2*Ka:-Ka>t?t+2*Ka:t,e]}}function ke(n){var t=Ee(n);return t.invert=Ee(-n),t}function Ae(n,t){function e(n,t){var e=Math.cos(t),o=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*r+o*i;return[Math.atan2(c*u-s*a,o*r-l*i),V(s*u+c*a)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),a=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),o=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*u-c*a;return[Math.atan2(c*u+l*a,o*r+s*i),V(s*r-o*i)]},e}function Ne(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,a,o){null!=i?(i=qe(e,i),u=qe(e,u),(a>0?u>i:i>u)&&(i+=2*a*Ka)):(i=n+2*a*Ka,u=n);for(var c,l=a*t,s=i;a>0?s>u:u>s;s-=l)o.point((c=Pt([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],c[1])}}function qe(n,t){var e=zt(t);e[0]-=n,Ft(e);var r=I(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Qa)%(2*Math.PI)}function Te(n,t,e){var r=ya.range(n,t-Qa,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function Ce(n,t,e){var r=ya.range(n,t-Qa,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function ze(n){return n.source}function De(n){return n.target}function je(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),a=Math.cos(r),o=Math.sin(r),c=i*Math.cos(n),l=i*Math.sin(n),s=a*Math.cos(e),f=a*Math.sin(e),h=2*Math.asin(Math.sqrt(B(r-t)+i*a*B(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*c+t*s,i=e*l+t*f,a=e*u+t*o;return[Math.atan2(i,r)*eo,Math.atan2(a,Math.sqrt(r*r+i*i))*eo]}:function(){return[n*eo,t*eo]};return p.distance=h,p}function Le(){function n(n,i){var u=Math.sin(i*=to),a=Math.cos(i),o=Math.abs((n*=to)-t),c=Math.cos(o);rc+=Math.atan2(Math.sqrt((o=a*Math.sin(o))*o+(o=r*u-e*a*c)*o),e*u+r*a*c),t=n,e=u,r=a}var t,e,r;ic.point=function(i,u){t=i*to,e=Math.sin(u*=to),r=Math.cos(u),ic.point=n},ic.lineEnd=function(){ic.point=ic.lineEnd=s}}function He(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),a=Math.cos(i);return[Math.atan2(n*u,r*a),Math.asin(r&&e*u/r)]},e}function Fe(n,t){function e(n,t){var e=Math.abs(Math.abs(t)-Ka/2)<Qa?0:a/Math.pow(i(t),u);return[e*Math.sin(u*n),a-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Ka/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),a=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=a-t,r=U(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(a/r,1/u))-Ka/2]},e):Oe}function Pe(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return Math.abs(i)<Qa?we:(e.invert=function(n,t){var e=u-t;return[Math.atan2(n,e)/i,u-U(i)*Math.sqrt(n*n+e*e)]},e)}function Oe(n,t){return[n,Math.log(Math.tan(Ka/4+t/2))]}function Ye(n){var t,e=xe(n),r=e.scale,i=e.translate,u=e.clipExtent;return e.scale=function(){var n=r.apply(e,arguments);return n===e?t?e.clipExtent(null):e:n},e.translate=function(){var n=i.apply(e,arguments);return n===e?t?e.clipExtent(null):e:n},e.clipExtent=function(n){var a=u.apply(e,arguments);if(a===e){if(t=null==n){var o=Ka*r(),c=i();u([[c[0]-o,c[1]-o],[c[0]+o,c[1]+o]])}}else t&&(a=null);return a},e.clipExtent(null)}function Re(n,t){var e=Math.cos(t)*Math.sin(n);return[Math.log((1+e)/(1-e))/2,Math.atan2(Math.tan(t),Math.cos(n))]}function Ue(n){function t(t){function a(){l.push("M",u(n(s),o))}for(var c,l=[],s=[],f=-1,h=t.length,g=pt(e),p=pt(r);++f<h;)i.call(this,c=t[f],f)?s.push([+g.call(this,c,f),+p.call(this,c,f)]):s.length&&(a(),s=[]);return s.length&&a(),l.length?l.join(""):null}var e=Ie,r=Ve,i=Xt,u=Xe,a=u.key,o=.7;return t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t.defined=function(n){return arguments.length?(i=n,t):i},t.interpolate=function(n){return arguments.length?(a="function"==typeof n?u=n:(u=sc.get(n)||Xe).key,t):a},t.tension=function(n){return arguments.length?(o=n,t):o},t}function Ie(n){return n[0]}function Ve(n){return n[1]}function Xe(n){return n.join("L")}function Ze(n){return Xe(n)+"Z"}function Be(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t<e;)i.push("H",(r[0]+(r=n[t])[0])/2,"V",r[1]);return e>1&&i.push("H",r[0]),i.join("")}function $e(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t<e;)i.push("V",(r=n[t])[1],"H",r[0]);return i.join("")}function We(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t<e;)i.push("H",(r=n[t])[0],"V",r[1]);return i.join("")}function Je(n,t){return n.length<4?Xe(n):n[1]+Qe(n.slice(1,n.length-1),nr(n,t))}function Ge(n,t){return n.length<3?Xe(n):n[0]+Qe((n.push(n[0]),n),nr([n[n.length-2]].concat(n,[n[1]]),t))}function Ke(n,t){return n.length<3?Xe(n):n[0]+Qe(n,nr(n,t))}function Qe(n,t){if(t.length<1||n.length!=t.length&&n.length!=t.length+2)return Xe(n);var e=n.length!=t.length,r="",i=n[0],u=n[1],a=t[0],o=a,c=1;if(e&&(r+="Q"+(u[0]-2*a[0]/3)+","+(u[1]-2*a[1]/3)+","+u[0]+","+u[1],i=n[1],c=2),t.length>1){o=t[1],u=n[c],c++,r+="C"+(i[0]+a[0])+","+(i[1]+a[1])+","+(u[0]-o[0])+","+(u[1]-o[1])+","+u[0]+","+u[1];for(var l=2;l<t.length;l++,c++)u=n[c],o=t[l],r+="S"+(u[0]-o[0])+","+(u[1]-o[1])+","+u[0]+","+u[1]}if(e){var s=n[c];r+="Q"+(u[0]+2*o[0]/3)+","+(u[1]+2*o[1]/3)+","+s[0]+","+s[1]}return r}function nr(n,t){for(var e,r=[],i=(1-t)/2,u=n[0],a=n[1],o=1,c=n.length;++o<c;)e=u,u=a,a=n[o],r.push([i*(a[0]-e[0]),i*(a[1]-e[1])]);return r}function tr(n){if(n.length<3)return Xe(n);var t=1,e=n.length,r=n[0],i=r[0],u=r[1],a=[i,i,i,(r=n[1])[0]],o=[u,u,u,r[1]],c=[i,",",u,"L",ur(gc,a),",",ur(gc,o)];for(n.push(n[e-1]);++t<=e;)r=n[t],a.shift(),a.push(r[0]),o.shift(),o.push(r[1]),ar(c,a,o);return n.pop(),c.push("L",r),c.join("")}function er(n){if(n.length<4)return Xe(n);for(var t,e=[],r=-1,i=n.length,u=[0],a=[0];++r<3;)t=n[r],u.push(t[0]),a.push(t[1]);for(e.push(ur(gc,u)+","+ur(gc,a)),--r;++r<i;)t=n[r],u.shift(),u.push(t[0]),a.shift(),a.push(t[1]),ar(e,u,a);return e.join("")}function rr(n){for(var t,e,r=-1,i=n.length,u=i+4,a=[],o=[];++r<4;)e=n[r%i],a.push(e[0]),o.push(e[1]);for(t=[ur(gc,a),",",ur(gc,o)],--r;++r<u;)e=n[r%i],a.shift(),a.push(e[0]),o.shift(),o.push(e[1]),ar(t,a,o);return t.join("")}function ir(n,t){var e=n.length-1;if(e)for(var r,i,u=n[0][0],a=n[0][1],o=n[e][0]-u,c=n[e][1]-a,l=-1;++l<=e;)r=n[l],i=l/e,r[0]=t*r[0]+(1-t)*(u+i*o),r[1]=t*r[1]+(1-t)*(a+i*c);return tr(n)}function ur(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]+n[3]*t[3]
+}function ar(n,t,e){n.push("C",ur(fc,t),",",ur(fc,e),",",ur(hc,t),",",ur(hc,e),",",ur(gc,t),",",ur(gc,e))}function or(n,t){return(t[1]-n[1])/(t[0]-n[0])}function cr(n){for(var t=0,e=n.length-1,r=[],i=n[0],u=n[1],a=r[0]=or(i,u);++t<e;)r[t]=(a+(a=or(i=u,u=n[t+1])))/2;return r[t]=a,r}function lr(n){for(var t,e,r,i,u=[],a=cr(n),o=-1,c=n.length-1;++o<c;)t=or(n[o],n[o+1]),Math.abs(t)<1e-6?a[o]=a[o+1]=0:(e=a[o]/t,r=a[o+1]/t,i=e*e+r*r,i>9&&(i=3*t/Math.sqrt(i),a[o]=i*e,a[o+1]=i*r));for(o=-1;++o<=c;)i=(n[Math.min(c,o+1)][0]-n[Math.max(0,o-1)][0])/(6*(1+a[o]*a[o])),u.push([i||0,a[o]*i||0]);return u}function sr(n){return n.length<3?Xe(n):n[0]+Qe(n,lr(n))}function fr(n,t,e,r){var i,u,a,o,c,l,s;return i=r[n],u=i[0],a=i[1],i=r[t],o=i[0],c=i[1],i=r[e],l=i[0],s=i[1],(s-a)*(o-u)-(c-a)*(l-u)>0}function hr(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function gr(n,t,e,r){var i=n[0],u=e[0],a=t[0]-i,o=r[0]-u,c=n[1],l=e[1],s=t[1]-c,f=r[1]-l,h=(o*(c-l)-f*(i-u))/(f*a-o*s);return[i+h*a,c+h*s]}function pr(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function mr(n,t){var e={list:n.map(function(n,t){return{index:t,x:n[0],y:n[1]}}).sort(function(n,t){return n.y<t.y?-1:n.y>t.y?1:n.x<t.x?-1:n.x>t.x?1:0}),bottomSite:null},r={list:[],leftEnd:null,rightEnd:null,init:function(){r.leftEnd=r.createHalfEdge(null,"l"),r.rightEnd=r.createHalfEdge(null,"l"),r.leftEnd.r=r.rightEnd,r.rightEnd.l=r.leftEnd,r.list.unshift(r.leftEnd,r.rightEnd)},createHalfEdge:function(n,t){return{edge:n,side:t,vertex:null,l:null,r:null}},insert:function(n,t){t.l=n,t.r=n.r,n.r.l=t,n.r=t},leftBound:function(n){var t=r.leftEnd;do t=t.r;while(t!=r.rightEnd&&i.rightOf(t,n));return t=t.l},del:function(n){n.l.r=n.r,n.r.l=n.l,n.edge=null},right:function(n){return n.r},left:function(n){return n.l},leftRegion:function(n){return null==n.edge?e.bottomSite:n.edge.region[n.side]},rightRegion:function(n){return null==n.edge?e.bottomSite:n.edge.region[mc[n.side]]}},i={bisect:function(n,t){var e={region:{l:n,r:t},ep:{l:null,r:null}},r=t.x-n.x,i=t.y-n.y,u=r>0?r:-r,a=i>0?i:-i;return e.c=n.x*r+n.y*i+.5*(r*r+i*i),u>a?(e.a=1,e.b=i/r,e.c/=r):(e.b=1,e.a=r/i,e.c/=i),e},intersect:function(n,t){var e=n.edge,r=t.edge;if(!e||!r||e.region.r==r.region.r)return null;var i=e.a*r.b-e.b*r.a;if(Math.abs(i)<1e-10)return null;var u,a,o=(e.c*r.b-r.c*e.b)/i,c=(r.c*e.a-e.c*r.a)/i,l=e.region.r,s=r.region.r;l.y<s.y||l.y==s.y&&l.x<s.x?(u=n,a=e):(u=t,a=r);var f=o>=a.region.r.x;return f&&"l"===u.side||!f&&"r"===u.side?null:{x:o,y:c}},rightOf:function(n,t){var e=n.edge,r=e.region.r,i=t.x>r.x;if(i&&"l"===n.side)return 1;if(!i&&"r"===n.side)return 0;if(1===e.a){var u=t.y-r.y,a=t.x-r.x,o=0,c=0;if(!i&&e.b<0||i&&e.b>=0?c=o=u>=e.b*a:(c=t.x+t.y*e.b>e.c,e.b<0&&(c=!c),c||(o=1)),!o){var l=r.x-e.region.l.x;c=e.b*(a*a-u*u)<l*u*(1+2*a/l+e.b*e.b),e.b<0&&(c=!c)}}else{var s=e.c-e.a*t.x,f=t.y-s,h=t.x-r.x,g=s-r.y;c=f*f>h*h+g*g}return"l"===n.side?c:!c},endPoint:function(n,e,r){n.ep[e]=r,n.ep[mc[e]]&&t(n)},distance:function(n,t){var e=n.x-t.x,r=n.y-t.y;return Math.sqrt(e*e+r*r)}},u={list:[],insert:function(n,t,e){n.vertex=t,n.ystar=t.y+e;for(var r=0,i=u.list,a=i.length;a>r;r++){var o=i[r];if(!(n.ystar>o.ystar||n.ystar==o.ystar&&t.x>o.vertex.x))break}i.splice(r,0,n)},del:function(n){for(var t=0,e=u.list,r=e.length;r>t&&e[t]!=n;++t);e.splice(t,1)},empty:function(){return 0===u.list.length},nextEvent:function(n){for(var t=0,e=u.list,r=e.length;r>t;++t)if(e[t]==n)return e[t+1];return null},min:function(){var n=u.list[0];return{x:n.vertex.x,y:n.ystar}},extractMin:function(){return u.list.shift()}};r.init(),e.bottomSite=e.list.shift();for(var a,o,c,l,s,f,h,g,p,m,d,v,y,M=e.list.shift();;)if(u.empty()||(a=u.min()),M&&(u.empty()||M.y<a.y||M.y==a.y&&M.x<a.x))o=r.leftBound(M),c=r.right(o),h=r.rightRegion(o),v=i.bisect(h,M),f=r.createHalfEdge(v,"l"),r.insert(o,f),m=i.intersect(o,f),m&&(u.del(o),u.insert(o,m,i.distance(m,M))),o=f,f=r.createHalfEdge(v,"r"),r.insert(o,f),m=i.intersect(f,c),m&&u.insert(f,m,i.distance(m,M)),M=e.list.shift();else{if(u.empty())break;o=u.extractMin(),l=r.left(o),c=r.right(o),s=r.right(c),h=r.leftRegion(o),g=r.rightRegion(c),d=o.vertex,i.endPoint(o.edge,o.side,d),i.endPoint(c.edge,c.side,d),r.del(o),u.del(c),r.del(c),y="l",h.y>g.y&&(p=h,h=g,g=p,y="r"),v=i.bisect(h,g),f=r.createHalfEdge(v,y),r.insert(l,f),i.endPoint(v,mc[y],d),m=i.intersect(l,f),m&&(u.del(l),u.insert(l,m,i.distance(m,h))),m=i.intersect(f,s),m&&u.insert(f,m,i.distance(m,h))}for(o=r.right(r.leftEnd);o!=r.rightEnd;o=r.right(o))t(o.edge)}function dr(n){return n.x}function vr(n){return n.y}function yr(){return{leaf:!0,nodes:[],point:null,x:null,y:null}}function Mr(n,t,e,r,i,u){if(!n(t,e,r,i,u)){var a=.5*(e+i),o=.5*(r+u),c=t.nodes;c[0]&&Mr(n,c[0],e,r,a,o),c[1]&&Mr(n,c[1],a,r,i,o),c[2]&&Mr(n,c[2],e,o,a,u),c[3]&&Mr(n,c[3],a,o,i,u)}}function xr(n,t){n=ya.rgb(n),t=ya.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,a=t.g-r,o=t.b-i;return function(n){return"#"+ct(Math.round(e+u*n))+ct(Math.round(r+a*n))+ct(Math.round(i+o*n))}}function br(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Sr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function _r(n,t){return t-=n=+n,function(e){return n+t*e}}function wr(n,t){var e,r,i,u,a,o=0,c=0,l=[],s=[];for(n+="",t+="",dc.lastIndex=0,r=0;e=dc.exec(t);++r)e.index&&l.push(t.substring(o,c=e.index)),s.push({i:l.length,x:e[0]}),l.push(null),o=dc.lastIndex;for(o<t.length&&l.push(t.substring(o)),r=0,u=s.length;(e=dc.exec(n))&&u>r;++r)if(a=s[r],a.x==e[0]){if(a.i)if(null==l[a.i+1])for(l[a.i-1]+=a.x,l.splice(a.i,1),i=r+1;u>i;++i)s[i].i--;else for(l[a.i-1]+=a.x+l[a.i+1],l.splice(a.i,2),i=r+1;u>i;++i)s[i].i-=2;else if(null==l[a.i+1])l[a.i]=a.x;else for(l[a.i]=a.x+l[a.i+1],l.splice(a.i+1,1),i=r+1;u>i;++i)s[i].i--;s.splice(r,1),u--,r--}else a.x=_r(parseFloat(e[0]),parseFloat(a.x));for(;u>r;)a=s.pop(),null==l[a.i+1]?l[a.i]=a.x:(l[a.i]=a.x+l[a.i+1],l.splice(a.i+1,1)),u--;return 1===l.length?null==l[0]?(a=s[0].x,function(n){return a(n)+""}):function(){return t}:function(n){for(r=0;u>r;++r)l[(a=s[r]).i]=a.x(n);return l.join("")}}function Sr(n,t){for(var e,r=ya.interpolators.length;--r>=0&&!(e=ya.interpolators[r](n,t)););return e}function Er(n,t){var e,r=[],i=[],u=n.length,a=t.length,o=Math.min(n.length,t.length);for(e=0;o>e;++e)r.push(Sr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;a>e;++e)i[e]=t[e];return function(n){for(e=0;o>e;++e)i[e]=r[e](n);return i}}function kr(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function Ar(n){return function(t){return 1-n(1-t)}}function Nr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function qr(n){return n*n}function Tr(n){return n*n*n}function Cr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function zr(n){return function(t){return Math.pow(t,n)}}function Dr(n){return 1-Math.cos(n*Ka/2)}function jr(n){return Math.pow(2,10*(n-1))}function Lr(n){return 1-Math.sqrt(1-n*n)}function Hr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/(2*Ka)*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,10*-r)*Math.sin(2*(r-e)*Ka/t)}}function Fr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Pr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Or(n,t){n=ya.hcl(n),t=ya.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,a=t.c-r,o=t.l-i;return isNaN(a)&&(a=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return J(e+u*n,r+a*n,i+o*n)+""}}function Yr(n,t){n=ya.hsl(n),t=ya.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,a=t.s-r,o=t.l-i;return isNaN(a)&&(a=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return R(e+u*n,r+a*n,i+o*n)+""}}function Rr(n,t){n=ya.lab(n),t=ya.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,a=t.a-r,o=t.b-i;return function(n){return Q(e+u*n,r+a*n,i+o*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Ir(n){var t=[n.a,n.b],e=[n.c,n.d],r=Xr(t),i=Vr(t,e),u=Xr(Zr(e,t,-i))||0;t[0]*e[1]<e[0]*t[1]&&(t[0]*=-1,t[1]*=-1,r*=-1,i*=-1),this.rotate=(r?Math.atan2(t[1],t[0]):Math.atan2(-e[0],e[1]))*eo,this.translate=[n.e,n.f],this.scale=[r,u],this.skew=u?Math.atan2(i,u)*eo:0}function Vr(n,t){return n[0]*t[0]+n[1]*t[1]}function Xr(n){var t=Math.sqrt(Vr(n,n));return t&&(n[0]/=t,n[1]/=t),t}function Zr(n,t,e){return n[0]+=e*t[0],n[1]+=e*t[1],n}function Br(n,t){var e,r=[],i=[],u=ya.transform(n),a=ya.transform(t),o=u.translate,c=a.translate,l=u.rotate,s=a.rotate,f=u.skew,h=a.skew,g=u.scale,p=a.scale;return o[0]!=c[0]||o[1]!=c[1]?(r.push("translate(",null,",",null,")"),i.push({i:1,x:_r(o[0],c[0])},{i:3,x:_r(o[1],c[1])})):c[0]||c[1]?r.push("translate("+c+")"):r.push(""),l!=s?(l-s>180?s+=360:s-l>180&&(l+=360),i.push({i:r.push(r.pop()+"rotate(",null,")")-2,x:_r(l,s)})):s&&r.push(r.pop()+"rotate("+s+")"),f!=h?i.push({i:r.push(r.pop()+"skewX(",null,")")-2,x:_r(f,h)}):h&&r.push(r.pop()+"skewX("+h+")"),g[0]!=p[0]||g[1]!=p[1]?(e=r.push(r.pop()+"scale(",null,",",null,")"),i.push({i:e-4,x:_r(g[0],p[0])},{i:e-2,x:_r(g[1],p[1])})):(1!=p[0]||1!=p[1])&&r.push(r.pop()+"scale("+p+")"),e=i.length,function(n){for(var t,u=-1;++u<e;)r[(t=i[u]).i]=t.x(n);return r.join("")}}function $r(n,t){return t=t-(n=+n)?1/(t-n):0,function(e){return(e-n)*t}}function Wr(n,t){return t=t-(n=+n)?1/(t-n):0,function(e){return Math.max(0,Math.min(1,(e-n)*t))}}function Jr(n){for(var t=n.source,e=n.target,r=Kr(t,e),i=[t];t!==r;)t=t.parent,i.push(t);for(var u=i.length;e!==r;)i.splice(u,0,e),e=e.parent;return i}function Gr(n){for(var t=[],e=n.parent;null!=e;)t.push(n),n=e,e=e.parent;return t.push(n),t}function Kr(n,t){if(n===t)return n;for(var e=Gr(n),r=Gr(t),i=e.pop(),u=r.pop(),a=null;i===u;)a=i,i=e.pop(),u=r.pop();return a}function Qr(n){n.fixed|=2}function ni(n){n.fixed&=-7}function ti(n){n.fixed|=4,n.px=n.x,n.py=n.y}function ei(n){n.fixed&=-5}function ri(n,t,e){var r=0,i=0;if(n.charge=0,!n.leaf)for(var u,a=n.nodes,o=a.length,c=-1;++c<o;)u=a[c],null!=u&&(ri(u,t,e),n.charge+=u.charge,r+=u.charge*u.cx,i+=u.charge*u.cy);if(n.point){n.leaf||(n.point.x+=Math.random()-.5,n.point.y+=Math.random()-.5);var l=t*e[n.point.index];n.charge+=n.pointCharge=l,r+=l*n.point.x,i+=l*n.point.y}n.cx=r/n.charge,n.cy=i/n.charge}function ii(n,t){return ya.rebind(n,t,"sort","children","value"),n.nodes=n,n.links=ci,n}function ui(n){return n.children}function ai(n){return n.value}function oi(n,t){return t.value-n.value}function ci(n){return ya.merge(n.map(function(n){return(n.children||[]).map(function(t){return{source:n,target:t}})}))}function li(n){return n.x}function si(n){return n.y}function fi(n,t,e){n.y0=t,n.y=e}function hi(n){return ya.range(n.length)}function gi(n){for(var t=-1,e=n[0].length,r=[];++t<e;)r[t]=0;return r}function pi(n){for(var t,e=1,r=0,i=n[0][1],u=n.length;u>e;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function mi(n){return n.reduce(di,0)}function di(n,t){return n+t[1]}function vi(n,t){return yi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function yi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function Mi(n){return[ya.min(n),ya.max(n)]}function xi(n,t){return n.parent==t.parent?1:2}function bi(n){var t=n.children;return t&&t.length?t[0]:n._tree.thread}function _i(n){var t,e=n.children;return e&&(t=e.length)?e[t-1]:n._tree.thread}function wi(n,t){var e=n.children;if(e&&(i=e.length))for(var r,i,u=-1;++u<i;)t(r=wi(e[u],t),n)>0&&(n=r);return n}function Si(n,t){return n.x-t.x}function Ei(n,t){return t.x-n.x}function ki(n,t){return n.depth-t.depth}function Ai(n,t){function e(n,r){var i=n.children;if(i&&(a=i.length))for(var u,a,o=null,c=-1;++c<a;)u=i[c],e(u,o),o=u;t(n,r)}e(n,null)}function Ni(n){for(var t,e=0,r=0,i=n.children,u=i.length;--u>=0;)t=i[u]._tree,t.prelim+=e,t.mod+=e,e+=t.shift+(r+=t.change)}function qi(n,t,e){n=n._tree,t=t._tree;var r=e/(t.number-n.number);n.change+=r,t.change-=r,t.shift+=e,t.prelim+=e,t.mod+=e}function Ti(n,t,e){return n._tree.ancestor.parent==t.parent?n._tree.ancestor:e}function Ci(n,t){return n.value-t.value}function zi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Di(n,t){n._pack_next=t,t._pack_prev=n}function ji(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Li(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(l=e.length)){var e,r,i,u,a,o,c,l,s=1/0,f=-1/0,h=1/0,g=-1/0;if(e.forEach(Hi),r=e[0],r.x=-r.r,r.y=0,t(r),l>1&&(i=e[1],i.x=i.r,i.y=0,t(i),l>2))for(u=e[2],Oi(r,i,u),t(u),zi(r,u),r._pack_prev=u,zi(u,i),i=r._pack_next,a=3;l>a;a++){Oi(r,i,u=e[a]);var p=0,m=1,d=1;for(o=i._pack_next;o!==i;o=o._pack_next,m++)if(ji(o,u)){p=1;break}if(1==p)for(c=r._pack_prev;c!==o._pack_prev&&!ji(c,u);c=c._pack_prev,d++);p?(d>m||m==d&&i.r<r.r?Di(r,i=o):Di(r=c,i),a--):(zi(r,u),i=u,t(u))}var v=(s+f)/2,y=(h+g)/2,M=0;for(a=0;l>a;a++)u=e[a],u.x-=v,u.y-=y,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Fi)}}function Hi(n){n._pack_next=n._pack_prev=n}function Fi(n){delete n._pack_next,delete n._pack_prev}function Pi(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,a=i.length;++u<a;)Pi(i[u],t,e,r)}function Oi(n,t,e){var r=n.r+e.r,i=t.x-n.x,u=t.y-n.y;if(r&&(i||u)){var a=t.r+e.r,o=i*i+u*u;a*=a,r*=r;var c=.5+(r-a)/(2*o),l=Math.sqrt(Math.max(0,2*a*(r+o)-(r-=o)*r-a*a))/(2*o);e.x=n.x+c*i+l*u,e.y=n.y+c*u-l*i}else e.x=n.x+r,e.y=n.y}function Yi(n){return 1+ya.max(n,function(n){return n.y})}function Ri(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Ui(n){var t=n.children;return t&&t.length?Ui(t[0]):n}function Ii(n){var t,e=n.children;return e&&(t=e.length)?Ii(e[t-1]):n}function Vi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Xi(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Zi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Bi(n){return n.rangeExtent?n.rangeExtent():Zi(n.range())}function $i(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Wi(n,t){var e,r=0,i=n.length-1,u=n[r],a=n[i];return u>a&&(e=r,r=i,i=e,e=u,u=a,a=e),n[r]=t.floor(u),n[i]=t.ceil(a),n}function Ji(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:kc}function Gi(n,t,e,r){var i=[],u=[],a=0,o=Math.min(n.length,t.length)-1;for(n[o]<n[0]&&(n=n.slice().reverse(),t=t.slice().reverse());++a<=o;)i.push(e(n[a-1],n[a])),u.push(r(t[a-1],t[a]));return function(t){var e=ya.bisect(n,t,1,o)-1;return u[e](i[e](t))}}function Ki(n,t,e,r){function i(){var i=Math.min(n.length,t.length)>2?Gi:$i,c=r?Wr:$r;return a=i(n,t,c,e),o=i(t,n,c,Sr),u}function u(n){return a(n)}var a,o;return u.invert=function(n){return o(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return ru(n,t)},u.tickFormat=function(t,e){return iu(n,t,e)},u.nice=function(t){return nu(n,t),i()},u.copy=function(){return Ki(n,t,e,r)},i()}function Qi(n,t){return ya.rebind(n,t,"range","rangeRound","interpolate","clamp")}function nu(n,t){return Wi(n,Ji(t?eu(n,t)[2]:tu(n)))}function tu(n){var t=Zi(n),e=t[1]-t[0];return Math.pow(10,Math.round(Math.log(e)/Math.LN10)-1)}function eu(n,t){var e=Zi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function ru(n,t){return ya.range.apply(ya,eu(n,t))}function iu(n,t,e){var r=-Math.floor(Math.log(eu(n,t)[2])/Math.LN10+.01);return ya.format(e?e.replace(wo,function(n,t,e,i,u,a,o,c,l,s){return[t,e,i,u,a,o,c,l||"."+(r-2*("%"===s)),s].join("")}):",."+r+"f")}function uu(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function a(t){return n(i(t))}return a.invert=function(t){return u(n.invert(t))},a.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),a):r},a.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),a):t},a.nice=function(){var t=Wi(r.map(i),e?Math:Nc);return n.domain(t),r=t.map(u),a},a.ticks=function(){var n=Zi(r),a=[];if(n.every(isFinite)){var o=n[0],c=n[1],l=Math.floor(i(o)),s=Math.ceil(i(c)),f=t%1?2:t;if(e){for(;s>l;l++)for(var h=1;f>h;h++)a.push(u(l)*h);a.push(u(l))}else for(a.push(u(l));l++<s;)for(var h=f-1;h>0;h--)a.push(u(l)*h);for(l=0;a[l]<o;l++);for(s=a.length;a[s-1]>c;s--);a=a.slice(l,s)}return a},a.tickFormat=function(n,t){if(!arguments.length)return Ac;arguments.length<2?t=Ac:"function"!=typeof t&&(t=ya.format(t));var r,o=Math.max(.1,n/a.ticks().length),c=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/u(c(i(n)+r))<=o?t(n):""}},a.copy=function(){return uu(n.copy(),t,e,r)},Qi(a,n)}function au(n,t,e){function r(t){return n(i(t))}var i=ou(t),u=ou(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return ru(e,n)},r.tickFormat=function(n,t){return iu(e,n,t)},r.nice=function(n){return r.domain(nu(e,n))},r.exponent=function(a){return arguments.length?(i=ou(t=a),u=ou(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return au(n.copy(),t,e)},Qi(r,n)}function ou(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function cu(n,t){function e(t){return a[((u.get(t)||u.set(t,n.push(t)))-1)%a.length]}function r(t,e){return ya.range(n.length).map(function(n){return t+e*n})}var u,a,o;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new i;for(var a,o=-1,c=r.length;++o<c;)u.has(a=r[o])||u.set(a,n.push(a));return e[t.t].apply(e,t.a)},e.range=function(n){return arguments.length?(a=n,o=0,t={t:"range",a:arguments},e):a},e.rangePoints=function(i,u){arguments.length<2&&(u=0);var c=i[0],l=i[1],s=(l-c)/(Math.max(1,n.length-1)+u);return a=r(n.length<2?(c+l)/2:c+s*u/2,s),o=0,t={t:"rangePoints",a:arguments},e},e.rangeBands=function(i,u,c){arguments.length<2&&(u=0),arguments.length<3&&(c=u);var l=i[1]<i[0],s=i[l-0],f=i[1-l],h=(f-s)/(n.length-u+2*c);return a=r(s+h*c,h),l&&a.reverse(),o=h*(1-u),t={t:"rangeBands",a:arguments},e},e.rangeRoundBands=function(i,u,c){arguments.length<2&&(u=0),arguments.length<3&&(c=u);var l=i[1]<i[0],s=i[l-0],f=i[1-l],h=Math.floor((f-s)/(n.length-u+2*c)),g=f-s-(n.length-u)*h;return a=r(s+Math.round(g/2),h),l&&a.reverse(),o=Math.round(h*(1-u)),t={t:"rangeRoundBands",a:arguments},e},e.rangeBand=function(){return o},e.rangeExtent=function(){return Zi(t.a[0])},e.copy=function(){return cu(n,t)},e.domain(n)}function lu(n,t){function e(){var e=0,u=t.length;for(i=[];++e<u;)i[e-1]=ya.quantile(n,e/u);return r}function r(n){return isNaN(n=+n)?void 0:t[ya.bisect(i,n)]}var i;return r.domain=function(t){return arguments.length?(n=t.filter(function(n){return!isNaN(n)}).sort(ya.ascending),e()):n},r.range=function(n){return arguments.length?(t=n,e()):t},r.quantiles=function(){return i},r.invertExtent=function(e){return e=t.indexOf(e),0>e?[0/0,0/0]:[e>0?i[e-1]:n[0],e<i.length?i[e]:n[n.length-1]]},r.copy=function(){return lu(n,t)},e()}function su(n,t,e){function r(t){return e[Math.max(0,Math.min(a,Math.floor(u*(t-n))))]}function i(){return u=e.length/(t-n),a=e.length-1,r}var u,a;return r.domain=function(e){return arguments.length?(n=+e[0],t=+e[e.length-1],i()):[n,t]},r.range=function(n){return arguments.length?(e=n,i()):e},r.invertExtent=function(t){return t=e.indexOf(t),t=0>t?0/0:t/u+n,[t,t+1/u]},r.copy=function(){return su(n,t,e)},i()}function fu(n,t){function e(e){return e>=e?t[ya.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return fu(n,t)},e}function hu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return ru(n,t)},t.tickFormat=function(t,e){return iu(n,t,e)},t.copy=function(){return hu(n)},t}function gu(n){return n.innerRadius}function pu(n){return n.outerRadius}function mu(n){return n.startAngle}function du(n){return n.endAngle}function vu(n){for(var t,e,r,i=-1,u=n.length;++i<u;)t=n[i],e=t[0],r=t[1]+Dc,t[0]=e*Math.cos(r),t[1]=e*Math.sin(r);return n}function yu(n){function t(t){function c(){m.push("M",o(n(v),f),s,l(n(d.reverse()),f),"Z")}for(var h,g,p,m=[],d=[],v=[],y=-1,M=t.length,x=pt(e),b=pt(i),_=e===r?function(){return g}:pt(r),w=i===u?function(){return p}:pt(u);++y<M;)a.call(this,h=t[y],y)?(d.push([g=+x.call(this,h,y),p=+b.call(this,h,y)]),v.push([+_.call(this,h,y),+w.call(this,h,y)])):d.length&&(c(),d=[],v=[]);return d.length&&c(),m.length?m.join(""):null}var e=Ie,r=Ie,i=0,u=Ve,a=Xt,o=Xe,c=o.key,l=o,s="L",f=.7;return t.x=function(n){return arguments.length?(e=r=n,t):r},t.x0=function(n){return arguments.length?(e=n,t):e},t.x1=function(n){return arguments.length?(r=n,t):r},t.y=function(n){return arguments.length?(i=u=n,t):u},t.y0=function(n){return arguments.length?(i=n,t):i},t.y1=function(n){return arguments.length?(u=n,t):u},t.defined=function(n){return arguments.length?(a=n,t):a},t.interpolate=function(n){return arguments.length?(c="function"==typeof n?o=n:(o=sc.get(n)||Xe).key,l=o.reverse||o,s=o.closed?"M":"L",t):c},t.tension=function(n){return arguments.length?(f=n,t):f},t}function Mu(n){return n.radius}function xu(n){return[n.x,n.y]}function bu(n){return function(){var t=n.apply(this,arguments),e=t[0],r=t[1]+Dc;return[e*Math.cos(r),e*Math.sin(r)]}}function _u(){return 64}function wu(){return"circle"}function Su(n){var t=Math.sqrt(n/Ka);return"M0,"+t+"A"+t+","+t+" 0 1,1 0,"+-t+"A"+t+","+t+" 0 1,1 0,"+t+"Z"}function Eu(n,t){return La(n,Yc),n.id=t,n}function ku(n,t,e,r){var i=n.id;return T(n,"function"==typeof e?function(n,u,a){n.__transition__[i].tween.set(t,r(e.call(n,n.__data__,u,a)))}:(e=r(e),function(n){n.__transition__[i].tween.set(t,e)}))}function Au(n){return null==n&&(n=""),function(){this.textContent=n}}function Nu(n,t,e,r){var u=n.__transition__||(n.__transition__={active:0,count:0}),a=u[e];if(!a){var o=r.time;a=u[e]={tween:new i,time:o,ease:r.ease,delay:r.delay,duration:r.duration},++u.count,ya.timer(function(r){function i(r){return u.active>e?l():(u.active=e,a.event&&a.event.start.call(n,s,t),a.tween.forEach(function(e,r){(r=r.call(n,s,t))&&p.push(r)}),c(r)?1:(xt(c,0,o),void 0))}function c(r){if(u.active!==e)return l();for(var i=(r-h)/g,o=f(i),c=p.length;c>0;)p[--c].call(n,o);return i>=1?(l(),a.event&&a.event.end.call(n,s,t),1):void 0}function l(){return--u.count?delete u[e]:delete n.__transition__,1}var s=n.__data__,f=a.ease,h=a.delay,g=a.duration,p=[];return r>=h?i(r):(xt(i,h,o),void 0)},0,o)}}function qu(n,t){n.attr("transform",function(n){return"translate("+t(n)+",0)"})}function Tu(n,t){n.attr("transform",function(n){return"translate(0,"+t(n)+")"})}function Cu(n,t,e){if(r=[],e&&t.length>1){for(var r,i,u,a=Zi(n.domain()),o=-1,c=t.length,l=(t[1]-t[0])/++e;++o<c;)for(i=e;--i>0;)(u=+t[o]-i*l)>=a[0]&&r.push(u);for(--o,i=0;++i<e&&(u=+t[o]+i*l)<a[1];)r.push(u)}return r}function zu(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function Du(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new Zc(e-1)),1),e}function u(n,e){return t(n=new Zc(+n),e),n}function a(n,r,u){var a=i(n),o=[];if(u>1)for(;r>a;)e(a)%u||o.push(new Date(+a)),t(a,1);else for(;r>a;)o.push(new Date(+a)),t(a,1);return o}function o(n,t,e){try{Zc=zu;var r=new zu;return r._=n,a(r,t,e)}finally{Zc=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=a;var c=n.utc=ju(n);return c.floor=c,c.round=ju(r),c.ceil=ju(i),c.offset=ju(u),c.range=o,n}function ju(n){return function(t,e){try{Zc=zu;var r=new zu;return r._=t,n(r,e)._}finally{Zc=Date}}}function Lu(n,t,e,r){for(var i,u,a=0,o=t.length,c=e.length;o>a;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(u=gl[t.charAt(a++)],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function Hu(n){return new RegExp("^(?:"+n.map(ya.requote).join("|")+")","i")}function Fu(n){for(var t=new i,e=-1,r=n.length;++e<r;)t.set(n[e].toLowerCase(),e);return t}function Pu(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Ou(n,t,e){il.lastIndex=0;var r=il.exec(t.substring(e));return r?(n.w=ul.get(r[0].toLowerCase()),e+r[0].length):-1}function Yu(n,t,e){el.lastIndex=0;var r=el.exec(t.substring(e));return r?(n.w=rl.get(r[0].toLowerCase()),e+r[0].length):-1}function Ru(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+1));return r?(n.w=+r[0],e+r[0].length):-1}function Uu(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e));return r?(n.U=+r[0],e+r[0].length):-1}function Iu(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e));return r?(n.W=+r[0],e+r[0].length):-1}function Vu(n,t,e){cl.lastIndex=0;var r=cl.exec(t.substring(e));return r?(n.m=ll.get(r[0].toLowerCase()),e+r[0].length):-1}function Xu(n,t,e){al.lastIndex=0;var r=al.exec(t.substring(e));return r?(n.m=ol.get(r[0].toLowerCase()),e+r[0].length):-1}function Zu(n,t,e){return Lu(n,hl.c.toString(),t,e)}function Bu(n,t,e){return Lu(n,hl.x.toString(),t,e)}function $u(n,t,e){return Lu(n,hl.X.toString(),t,e)}function Wu(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+4));return r?(n.y=+r[0],e+r[0].length):-1}function Ju(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+2));return r?(n.y=Gu(+r[0]),e+r[0].length):-1}function Gu(n){return n+(n>68?1900:2e3)}function Ku(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function Qu(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function na(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function ta(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ea(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ra(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ia(n,t,e){pl.lastIndex=0;var r=pl.exec(t.substring(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ua(n,t,e){var r=ml.get(t.substring(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}function aa(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=~~(Math.abs(t)/60),i=Math.abs(t)%60;return e+Pu(r,"0",2)+Pu(i,"0",2)}function oa(n,t,e){sl.lastIndex=0;var r=sl.exec(t.substring(e,e+1));return r?e+r[0].length:-1}function ca(n){return n.toISOString()}function la(n,t,e){function r(t){return n(t)}return r.invert=function(t){return sa(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain(t),r):n.domain().map(sa)},r.nice=function(n){return r.domain(Wi(r.domain(),n))},r.ticks=function(e,i){var u=Zi(r.domain());if("function"!=typeof e){var a=u[1]-u[0],o=a/e,c=ya.bisect(vl,o);if(c==vl.length)return t.year(u,e);if(!c)return n.ticks(e).map(sa);o/vl[c-1]<vl[c]/o&&--c,e=t[c],i=e[1],e=e[0].range}return e(u[0],new Date(+u[1]+1),i)},r.tickFormat=function(){return e},r.copy=function(){return la(n.copy(),t,e)},Qi(r,n)}function sa(n){return new Date(n)}function fa(n){return function(t){for(var e=n.length-1,r=n[e];!r[1](t);)r=n[--e];return r[0](t)}}function ha(n){var t=new Date(n,0,1);return t.setFullYear(n),t}function ga(n){var t=n.getFullYear(),e=ha(t),r=ha(t+1);return t+(n-e)/(r-e)}function pa(n){var t=new Date(Date.UTC(n,0,1));return t.setUTCFullYear(n),t}function ma(n){var t=n.getUTCFullYear(),e=pa(t),r=pa(t+1);return t+(n-e)/(r-e)}function da(n){return JSON.parse(n.responseText)}function va(n){var t=Ma.createRange();return t.selectNode(Ma.body),t.createContextualFragment(n.responseText)}var ya={version:"3.2.7"};Date.now||(Date.now=function(){return+new Date});var Ma=document,xa=Ma.documentElement,ba=window;try{Ma.createElement("div").style.setProperty("opacity",0,"")}catch(_a){var wa=ba.Element.prototype,Sa=wa.setAttribute,Ea=wa.setAttributeNS,ka=ba.CSSStyleDeclaration.prototype,Aa=ka.setProperty;wa.setAttribute=function(n,t){Sa.call(this,n,t+"")},wa.setAttributeNS=function(n,t,e){Ea.call(this,n,t,e+"")},ka.setProperty=function(n,t,e){Aa.call(this,n,t+"",e)}}ya.ascending=function(n,t){return t>n?-1:n>t?1:n>=t?0:0/0},ya.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:0/0},ya.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i<u&&!(null!=(e=n[i])&&e>=e);)e=void 0;for(;++i<u;)null!=(r=n[i])&&e>r&&(e=r)}else{for(;++i<u&&!(null!=(e=t.call(n,n[i],i))&&e>=e);)e=void 0;for(;++i<u;)null!=(r=t.call(n,n[i],i))&&e>r&&(e=r)}return e},ya.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i<u&&!(null!=(e=n[i])&&e>=e);)e=void 0;for(;++i<u;)null!=(r=n[i])&&r>e&&(e=r)}else{for(;++i<u&&!(null!=(e=t.call(n,n[i],i))&&e>=e);)e=void 0;for(;++i<u;)null!=(r=t.call(n,n[i],i))&&r>e&&(e=r)}return e},ya.extent=function(n,t){var e,r,i,u=-1,a=n.length;if(1===arguments.length){for(;++u<a&&!(null!=(e=i=n[u])&&e>=e);)e=i=void 0;for(;++u<a;)null!=(r=n[u])&&(e>r&&(e=r),r>i&&(i=r))}else{for(;++u<a&&!(null!=(e=i=t.call(n,n[u],u))&&e>=e);)e=void 0;for(;++u<a;)null!=(r=t.call(n,n[u],u))&&(e>r&&(e=r),r>i&&(i=r))}return[e,i]},ya.sum=function(n,t){var e,r=0,i=n.length,u=-1;if(1===arguments.length)for(;++u<i;)isNaN(e=+n[u])||(r+=e);else for(;++u<i;)isNaN(e=+t.call(n,n[u],u))||(r+=e);return r},ya.mean=function(t,e){var r,i=t.length,u=0,a=-1,o=0;if(1===arguments.length)for(;++a<i;)n(r=t[a])&&(u+=(r-u)/++o);else for(;++a<i;)n(r=e.call(t,t[a],a))&&(u+=(r-u)/++o);return o?u:void 0},ya.quantile=function(n,t){var e=(n.length-1)*t+1,r=Math.floor(e),i=+n[r-1],u=e-r;return u?i+u*(n[r]-i):i},ya.median=function(t,e){return arguments.length>1&&(t=t.map(e)),t=t.filter(n),t.length?ya.quantile(t.sort(ya.ascending),.5):void 0},ya.bisector=function(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n.call(t,t[u],u)<e?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;e<n.call(t,t[u],u)?i=u:r=u+1}return r}}};var Na=ya.bisector(function(n){return n});ya.bisectLeft=Na.left,ya.bisect=ya.bisectRight=Na.right,ya.shuffle=function(n){for(var t,e,r=n.length;r;)e=0|Math.random()*r--,t=n[r],n[r]=n[e],n[e]=t;return n},ya.permute=function(n,t){for(var e=[],r=-1,i=t.length;++r<i;)e[r]=n[t[r]];return e},ya.zip=function(){if(!(i=arguments.length))return[];for(var n=-1,e=ya.min(arguments,t),r=new Array(e);++n<e;)for(var i,u=-1,a=r[n]=new Array(i);++u<i;)a[u]=arguments[u][n];return r},ya.transpose=function(n){return ya.zip.apply(ya,n)},ya.keys=function(n){var t=[];for(var e in n)t.push(e);return t},ya.values=function(n){var t=[];for(var e in n)t.push(n[e]);return t},ya.entries=function(n){var t=[];for(var e in n)t.push({key:e,value:n[e]});return t},ya.merge=function(n){return Array.prototype.concat.apply([],n)},ya.range=function(n,t,r){if(arguments.length<3&&(r=1,arguments.length<2&&(t=n,n=0)),1/0===(t-n)/r)throw new Error("infinite range");var i,u=[],a=e(Math.abs(r)),o=-1;if(n*=a,t*=a,r*=a,0>r)for(;(i=n+r*++o)>t;)u.push(i/a);else for(;(i=n+r*++o)<t;)u.push(i/a);return u},ya.map=function(n){var t=new i;for(var e in n)t.set(e,n[e]);return t},r(i,{has:function(n){return qa+n in this},get:function(n){return this[qa+n]},set:function(n,t){return this[qa+n]=t},remove:function(n){return n=qa+n,n in this&&delete this[n]},keys:function(){var n=[];return this.forEach(function(t){n.push(t)}),n},values:function(){var n=[];return this.forEach(function(t,e){n.push(e)}),n},entries:function(){var n=[];return this.forEach(function(t,e){n.push({key:t,value:e})
+}),n},forEach:function(n){for(var t in this)t.charCodeAt(0)===Ta&&n.call(this,t.substring(1),this[t])}});var qa="\0",Ta=qa.charCodeAt(0);ya.nest=function(){function n(t,o,c){if(c>=a.length)return r?r.call(u,o):e?o.sort(e):o;for(var l,s,f,h,g=-1,p=o.length,m=a[c++],d=new i;++g<p;)(h=d.get(l=m(s=o[g])))?h.push(s):d.set(l,[s]);return t?(s=t(),f=function(e,r){s.set(e,n(t,r,c))}):(s={},f=function(e,r){s[e]=n(t,r,c)}),d.forEach(f),s}function t(n,e){if(e>=a.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,u={},a=[],o=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(ya.map,e,0),0)},u.key=function(n){return a.push(n),u},u.sortKeys=function(n){return o[a.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},ya.set=function(n){var t=new u;if(n)for(var e=0;e<n.length;e++)t.add(n[e]);return t},r(u,{has:function(n){return qa+n in this},add:function(n){return this[qa+n]=!0,n},remove:function(n){return n=qa+n,n in this&&delete this[n]},values:function(){var n=[];return this.forEach(function(t){n.push(t)}),n},forEach:function(n){for(var t in this)t.charCodeAt(0)===Ta&&n.call(this,t.substring(1))}}),ya.behavior={},ya.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r<i;)n[e=arguments[r]]=a(n,t,t[e]);return n};var Ca=["webkit","ms","moz","Moz","o","O"],za=l;try{za(xa.childNodes)[0].nodeType}catch(Da){za=c}ya.dispatch=function(){for(var n=new f,t=-1,e=arguments.length;++t<e;)n[arguments[t]]=h(n);return n},f.prototype.on=function(n,t){var e=n.indexOf("."),r="";if(e>=0&&(r=n.substring(e+1),n=n.substring(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ya.event=null,ya.requote=function(n){return n.replace(ja,"\\$&")};var ja=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,La={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},Ha=function(n,t){return t.querySelector(n)},Fa=function(n,t){return t.querySelectorAll(n)},Pa=xa[o(xa,"matchesSelector")],Oa=function(n,t){return Pa.call(n,t)};"function"==typeof Sizzle&&(Ha=function(n,t){return Sizzle(n,t)[0]||null},Fa=function(n,t){return Sizzle.uniqueSort(Sizzle(n,t))},Oa=Sizzle.matchesSelector),ya.selection=function(){return Ia};var Ya=ya.selection.prototype=[];Ya.select=function(n){var t,e,r,i,u=[];n=v(n);for(var a=-1,o=this.length;++a<o;){u.push(t=[]),t.parentNode=(r=this[a]).parentNode;for(var c=-1,l=r.length;++c<l;)(i=r[c])?(t.push(e=n.call(i,i.__data__,c,a)),e&&"__data__"in i&&(e.__data__=i.__data__)):t.push(null)}return d(u)},Ya.selectAll=function(n){var t,e,r=[];n=y(n);for(var i=-1,u=this.length;++i<u;)for(var a=this[i],o=-1,c=a.length;++o<c;)(e=a[o])&&(r.push(t=za(n.call(e,e.__data__,o,i))),t.parentNode=e);return d(r)};var Ra={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};ya.ns={prefix:Ra,qualify:function(n){var t=n.indexOf(":"),e=n;return t>=0&&(e=n.substring(0,t),n=n.substring(t+1)),Ra.hasOwnProperty(e)?{space:Ra[e],local:n}:n}},Ya.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ya.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(M(t,n[t]));return this}return this.each(M(n,t))},Ya.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=n.trim().split(/^|\s+/g)).length,i=-1;if(t=e.classList){for(;++i<r;)if(!t.contains(n[i]))return!1}else for(t=e.getAttribute("class");++i<r;)if(!b(n[i]).test(t))return!1;return!0}for(t in n)this.each(_(t,n[t]));return this}return this.each(_(n,t))},Ya.style=function(n,t,e){var r=arguments.length;if(3>r){if("string"!=typeof n){2>r&&(t="");for(e in n)this.each(S(e,n[e],t));return this}if(2>r)return ba.getComputedStyle(this.node(),null).getPropertyValue(n);e=""}return this.each(S(n,t,e))},Ya.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(E(t,n[t]));return this}return this.each(E(n,t))},Ya.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Ya.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Ya.append=function(n){return n=k(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Ya.insert=function(n,t){return n=k(n),t=v(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments))})},Ya.remove=function(){return this.each(function(){var n=this.parentNode;n&&n.removeChild(this)})},Ya.data=function(n,t){function e(n,e){var r,u,a,o=n.length,f=e.length,h=Math.min(o,f),g=new Array(f),p=new Array(f),m=new Array(o);if(t){var d,v=new i,y=new i,M=[];for(r=-1;++r<o;)d=t.call(u=n[r],u.__data__,r),v.has(d)?m[r]=u:v.set(d,u),M.push(d);for(r=-1;++r<f;)d=t.call(e,a=e[r],r),(u=v.get(d))?(g[r]=u,u.__data__=a):y.has(d)||(p[r]=A(a)),y.set(d,a),v.remove(d);for(r=-1;++r<o;)v.has(M[r])&&(m[r]=n[r])}else{for(r=-1;++r<h;)u=n[r],a=e[r],u?(u.__data__=a,g[r]=u):p[r]=A(a);for(;f>r;++r)p[r]=A(e[r]);for(;o>r;++r)m[r]=n[r]}p.update=g,p.parentNode=g.parentNode=m.parentNode=n.parentNode,c.push(p),l.push(g),s.push(m)}var r,u,a=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++a<o;)(u=r[a])&&(n[a]=u.__data__);return n}var c=C([]),l=d([]),s=d([]);if("function"==typeof n)for(;++a<o;)e(r=this[a],n.call(r,r.parentNode.__data__,a));else for(;++a<o;)e(r=this[a],n);return l.enter=function(){return c},l.exit=function(){return s},l},Ya.datum=function(n){return arguments.length?this.property("__data__",n):this.property("__data__")},Ya.filter=function(n){var t,e,r,i=[];"function"!=typeof n&&(n=N(n));for(var u=0,a=this.length;a>u;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var o=0,c=e.length;c>o;o++)(r=e[o])&&n.call(r,r.__data__,o)&&t.push(r)}return d(i)},Ya.order=function(){for(var n=-1,t=this.length;++n<t;)for(var e,r=this[n],i=r.length-1,u=r[i];--i>=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Ya.sort=function(n){n=q.apply(this,arguments);for(var t=-1,e=this.length;++t<e;)this[t].sort(n);return this.order()},Ya.each=function(n){return T(this,function(t,e,r){n.call(t,t.__data__,e,r)})},Ya.call=function(n){var t=za(arguments);return n.apply(t[0]=this,t),this},Ya.empty=function(){return!this.node()},Ya.node=function(){for(var n=0,t=this.length;t>n;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Ya.size=function(){var n=0;return this.each(function(){++n}),n};var Ua=[];ya.selection.enter=C,ya.selection.enter.prototype=Ua,Ua.append=Ya.append,Ua.empty=Ya.empty,Ua.node=Ya.node,Ua.call=Ya.call,Ua.size=Ya.size,Ua.select=function(n){for(var t,e,r,i,u,a=[],o=-1,c=this.length;++o<c;){r=(i=this[o]).update,a.push(t=[]),t.parentNode=i.parentNode;for(var l=-1,s=i.length;++l<s;)(u=i[l])?(t.push(r[l]=e=n.call(i.parentNode,u.__data__,l,o)),e.__data__=u.__data__):t.push(null)}return d(a)},Ua.insert=function(n,t){return arguments.length<2&&(t=z(this)),Ya.insert.call(this,n,t)},Ya.transition=function(){for(var n,t,e=Hc||++Rc,r=[],i=Fc||{time:Date.now(),ease:Cr,delay:0,duration:250},u=-1,a=this.length;++u<a;){r.push(n=[]);for(var o=this[u],c=-1,l=o.length;++c<l;)(t=o[c])&&Nu(t,c,e,i),n.push(t)}return Eu(r,e)},ya.select=function(n){var t=["string"==typeof n?Ha(n,Ma):n];return t.parentNode=xa,d([t])},ya.selectAll=function(n){var t=za("string"==typeof n?Fa(n,Ma):n);return t.parentNode=xa,d([t])};var Ia=ya.select(xa);Ya.on=function(n,t,e){var r=arguments.length;if(3>r){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(D(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(D(n,t,e))};var Va=ya.map({mouseenter:"mouseover",mouseleave:"mouseout"});Va.forEach(function(n){"on"+n in Ma&&Va.remove(n)});var Xa=o(xa.style,"userSelect"),Za=0;ya.mouse=function(n){return F(n,p())};var Ba=/WebKit/.test(ba.navigator.userAgent)?-1:0;ya.touches=function(n,t){return arguments.length<2&&(t=p().touches),t?za(t).map(function(t){var e=F(n,t);return e.identifier=t.identifier,e}):[]},ya.behavior.drag=function(){function n(){this.on("mousedown.drag",a).on("touchstart.drag",o)}function t(){return ya.event.changedTouches[0].identifier}function e(n,t){return ya.touches(n).filter(function(n){return n.identifier===t})[0]}function r(n,t,e,r){return function(){function a(){if(!s)return o();var n=t(s,g),e=n[0]-m[0],r=n[1]-m[1];d|=e|r,m=n,f({type:"drag",x:n[0]+c[0],y:n[1]+c[1],dx:e,dy:r})}function o(){v.on(e+"."+p,null).on(r+"."+p,null),y(d&&ya.event.target===h),f({type:"dragend"})}var c,l=this,s=l.parentNode,f=i.of(l,arguments),h=ya.event.target,g=n(),p=null==g?"drag":"drag-"+g,m=t(s,g),d=0,v=ya.select(ba).on(e+"."+p,a).on(r+"."+p,o),y=H();u?(c=u.apply(l,arguments),c=[c.x-m[0],c.y-m[1]]):c=[0,0],f({type:"dragstart"})}}var i=m(n,"drag","dragstart","dragend"),u=null,a=r(s,ya.mouse,"mousemove","mouseup"),o=r(t,e,"touchmove","touchend");return n.origin=function(t){return arguments.length?(u=t,n):u},ya.rebind(n,i,"on")},ya.behavior.zoom=function(){function n(){this.on(w,o).on(Ja+".zoom",l).on(S,s).on("dblclick.zoom",f).on("touchstart.zoom",c)}function t(n){return[(n[0]-x[0])/b,(n[1]-x[1])/b]}function e(n){return[n[0]*b+x[0],n[1]*b+x[1]]}function r(n){b=Math.max(_[0],Math.min(_[1],n))}function i(n,t){t=e(t),x[0]+=n[0]-t[0],x[1]+=n[1]-t[1]}function u(){d&&d.domain(p.range().map(function(n){return(n-x[0])/b}).map(p.invert)),y&&y.domain(v.range().map(function(n){return(n-x[1])/b}).map(v.invert))}function a(n){u(),n({type:"zoom",scale:b,translate:x})}function o(){function n(){c=1,i(ya.mouse(r),f),a(u)}function e(){l.on(S,ba===r?s:null).on(E,null),h(c&&ya.event.target===o)}var r=this,u=k.of(r,arguments),o=ya.event.target,c=0,l=ya.select(ba).on(S,n).on(E,e),f=t(ya.mouse(r)),h=H()}function c(){function n(){var n=ya.touches(u),t=n[0],e=s[t.identifier];if(o=n[1]){var o,l=s[o.identifier],g=ya.event.scale;if(null==g){var p=(p=o[0]-t[0])*p+(p=o[1]-t[1])*p;g=f&&Math.sqrt(p/f)}t=[(t[0]+o[0])/2,(t[1]+o[1])/2],e=[(e[0]+l[0])/2,(e[1]+l[1])/2],r(g*h)}M=null,i(t,e),a(c)}function e(){y.on(d,null).on(v,null),x.on(w,o),_()}var u=this,c=k.of(u,arguments),l=ya.touches(u),s={},f=0,h=b,p=Date.now(),m="zoom-"+ya.event.changedTouches[0].identifier,d="touchmove."+m,v="touchend."+m,y=ya.select(ba).on(d,n).on(v,e),x=ya.select(u).on(w,null),_=H();if(l.forEach(function(n){s[n.identifier]=t(n)}),1===l.length){if(500>p-M){var S=l[0],E=t(l[0]);r(2*b),i(S,E),g(),a(c)}M=p}else if(l.length>1){var S=l[0],A=l[1],N=S[0]-A[0],q=S[1]-A[1];f=N*N+q*q}}function l(){g(),h||(h=t(ya.mouse(this))),r(Math.pow(2,.002*$a())*b),i(ya.mouse(this),h),a(k.of(this,arguments))}function s(){h=null}function f(){var n=ya.mouse(this),e=t(n),u=Math.log(b)/Math.LN2;r(Math.pow(2,ya.event.shiftKey?Math.ceil(u)-1:Math.floor(u)+1)),i(n,e),a(k.of(this,arguments))}var h,p,d,v,y,M,x=[0,0],b=1,_=Wa,w="mousedown.zoom",S="mousemove.zoom",E="mouseup.zoom",k=m(n,"zoom");return n.translate=function(t){return arguments.length?(x=t.map(Number),u(),n):x},n.scale=function(t){return arguments.length?(b=+t,u(),n):b},n.scaleExtent=function(t){return arguments.length?(_=null==t?Wa:t.map(Number),n):_},n.x=function(t){return arguments.length?(d=t,p=t.copy(),x=[0,0],b=1,n):d},n.y=function(t){return arguments.length?(y=t,v=t.copy(),x=[0,0],b=1,n):y},ya.rebind(n,k,"on")};var $a,Wa=[0,1/0],Ja="onwheel"in Ma?($a=function(){return-ya.event.deltaY*(ya.event.deltaMode?120:1)},"wheel"):"onmousewheel"in Ma?($a=function(){return ya.event.wheelDelta},"mousewheel"):($a=function(){return-ya.event.detail},"MozMousePixelScroll");P.prototype.toString=function(){return this.rgb()+""},ya.hsl=function(n,t,e){return 1===arguments.length?n instanceof Y?O(n.h,n.s,n.l):lt(""+n,st,O):O(+n,+t,+e)};var Ga=Y.prototype=new P;Ga.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),O(this.h,this.s,this.l/n)},Ga.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),O(this.h,this.s,n*this.l)},Ga.rgb=function(){return R(this.h,this.s,this.l)};var Ka=Math.PI,Qa=1e-6,no=Qa*Qa,to=Ka/180,eo=180/Ka;ya.hcl=function(n,t,e){return 1===arguments.length?n instanceof W?$(n.h,n.c,n.l):n instanceof K?nt(n.l,n.a,n.b):nt((n=ft((n=ya.rgb(n)).r,n.g,n.b)).l,n.a,n.b):$(+n,+t,+e)};var ro=W.prototype=new P;ro.brighter=function(n){return $(this.h,this.c,Math.min(100,this.l+io*(arguments.length?n:1)))},ro.darker=function(n){return $(this.h,this.c,Math.max(0,this.l-io*(arguments.length?n:1)))},ro.rgb=function(){return J(this.h,this.c,this.l).rgb()},ya.lab=function(n,t,e){return 1===arguments.length?n instanceof K?G(n.l,n.a,n.b):n instanceof W?J(n.l,n.c,n.h):ft((n=ya.rgb(n)).r,n.g,n.b):G(+n,+t,+e)};var io=18,uo=.95047,ao=1,oo=1.08883,co=K.prototype=new P;co.brighter=function(n){return G(Math.min(100,this.l+io*(arguments.length?n:1)),this.a,this.b)},co.darker=function(n){return G(Math.max(0,this.l-io*(arguments.length?n:1)),this.a,this.b)},co.rgb=function(){return Q(this.l,this.a,this.b)},ya.rgb=function(n,t,e){return 1===arguments.length?n instanceof ot?at(n.r,n.g,n.b):lt(""+n,at,R):at(~~n,~~t,~~e)};var lo=ot.prototype=new P;lo.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),at(Math.min(255,~~(t/n)),Math.min(255,~~(e/n)),Math.min(255,~~(r/n)))):at(i,i,i)},lo.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),at(~~(n*this.r),~~(n*this.g),~~(n*this.b))},lo.hsl=function(){return st(this.r,this.g,this.b)},lo.toString=function(){return"#"+ct(this.r)+ct(this.g)+ct(this.b)};var so=ya.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});so.forEach(function(n,t){so.set(n,it(t))}),ya.functor=pt,ya.xhr=dt(mt),ya.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var a=ya.xhr(n,t,u);return a.row=function(n){return arguments.length?a.response(null==(e=n)?r:i(n)):e},a.row(e)}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function a(t){return t.map(o).join(n)}function o(n){return c.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var c=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(s>=c)return a;if(i)return i=!1,u;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++<c;)if(34===n.charCodeAt(e)){if(34!==n.charCodeAt(e+1))break;++e}s=e+2;var r=n.charCodeAt(e+1);return 13===r?(i=!0,10===n.charCodeAt(e+2)&&++s):10===r&&(i=!0),n.substring(t+1,e).replace(/""/g,'"')}for(;c>s;){var r=n.charCodeAt(s++),o=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(s)&&(++s,++o);else if(r!==l)continue;return n.substring(t,s-o)}return n.substring(t)}for(var r,i,u={},a={},o=[],c=n.length,s=0,f=0;(r=e())!==a;){for(var h=[];r!==u&&r!==a;)h.push(r),r=e();(!t||(h=t(h,f++)))&&o.push(h)}return o},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new u,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(a).join("\n")},e},ya.csv=ya.dsv(",","text/csv"),ya.tsv=ya.dsv("	","text/tab-separated-values");var fo,ho,go,po,mo,vo=ba[o(ba,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ya.timer=function(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={callback:n,time:i,next:null};ho?ho.next=u:fo=u,ho=u,go||(po=clearTimeout(po),go=1,vo(Mt))},ya.timer.flush=function(){bt(),_t()};var yo=".",Mo=",",xo=[3,3],bo="$",_o=["y","z","a","f","p","n","Âµ","m","","k","M","G","T","P","E","Z","Y"].map(wt);ya.formatPrefix=function(n,t){var e=0;return n&&(0>n&&(n*=-1),t&&(n=ya.round(n,St(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((0>=e?e+1:e-1)/3)))),_o[8+e/3]},ya.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)},ya.format=function(n){var t=wo.exec(n),e=t[1]||" ",r=t[2]||">",i=t[3]||"",u=t[4]||"",a=t[5],o=+t[6],c=t[7],l=t[8],s=t[9],f=1,h="",g=!1;switch(l&&(l=+l.substring(1)),(a||"0"===e&&"="===r)&&(a=e="0",r="=",c&&(o-=Math.floor((o-1)/4))),s){case"n":c=!0,s="g";break;case"%":f=100,h="%",s="f";break;case"p":f=100,h="%",s="r";break;case"b":case"o":case"x":case"X":"#"===u&&(u="0"+s.toLowerCase());case"c":case"d":g=!0,l=0;break;case"s":f=-1,s="r"}"#"===u?u="":"$"===u&&(u=bo),"r"!=s||l||(s="g"),null!=l&&("g"==s?l=Math.max(1,Math.min(21,l)):("e"==s||"f"==s)&&(l=Math.max(0,Math.min(20,l)))),s=So.get(s)||Et;var p=a&&c;return function(n){if(g&&n%1)return"";var t=0>n||0===n&&0>1/n?(n=-n,"-"):i;if(0>f){var m=ya.formatPrefix(n,l);n=m.scale(n),h=m.symbol}else n*=f;n=s(n,l);var d=n.lastIndexOf("."),v=0>d?n:n.substring(0,d),y=0>d?"":yo+n.substring(d+1);!a&&c&&(v=Eo(v));var M=u.length+v.length+y.length+(p?0:t.length),x=o>M?new Array(M=o-M+1).join(e):"";return p&&(v=Eo(x+v)),t+=u,n=v+y,("<"===r?t+n+x:">"===r?x+t+n:"^"===r?x.substring(0,M>>=1)+t+n+x.substring(M):t+(p?n:x+n))+h}};var wo=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,So=ya.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ya.round(n,St(n,t))).toFixed(Math.max(0,Math.min(20,St(n*(1+1e-15),t))))}}),Eo=mt;if(xo){var ko=xo.length;Eo=function(n){for(var t=n.length,e=[],r=0,i=xo[0];t>0&&i>0;)e.push(n.substring(t-=i,t+i)),i=xo[r=(r+1)%ko];return e.reverse().join(Mo)}}ya.geo={},kt.prototype={s:0,t:0,add:function(n){At(n,this.t,Ao),At(Ao.s,this.s,this),this.s?this.t+=Ao.t:this.s=Ao.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var Ao=new kt;ya.geo.stream=function(n,t){n&&No.hasOwnProperty(n.type)?No[n.type](n,t):Nt(n,t)};var No={Feature:function(n,t){Nt(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++r<i;)Nt(e[r].geometry,t)}},qo={Sphere:function(n,t){t.sphere()},Point:function(n,t){var e=n.coordinates;t.point(e[0],e[1])},MultiPoint:function(n,t){for(var e,r=n.coordinates,i=-1,u=r.length;++i<u;)e=r[i],t.point(e[0],e[1])},LineString:function(n,t){qt(n.coordinates,t,0)},MultiLineString:function(n,t){for(var e=n.coordinates,r=-1,i=e.length;++r<i;)qt(e[r],t,0)},Polygon:function(n,t){Tt(n.coordinates,t)},MultiPolygon:function(n,t){for(var e=n.coordinates,r=-1,i=e.length;++r<i;)Tt(e[r],t)},GeometryCollection:function(n,t){for(var e=n.geometries,r=-1,i=e.length;++r<i;)Nt(e[r],t)}};ya.geo.area=function(n){return To=0,ya.geo.stream(n,zo),To};var To,Co=new kt,zo={sphere:function(){To+=4*Ka},point:s,lineStart:s,lineEnd:s,polygonStart:function(){Co.reset(),zo.lineStart=Ct},polygonEnd:function(){var n=2*Co;To+=0>n?4*Ka+n:n,zo.lineStart=zo.lineEnd=zo.point=s}};ya.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=zt([t*to,e*to]);if(v){var i=jt(v,r),u=[i[1],-i[0],0],a=jt(u,i);Ft(a),a=Pt(a);var c=t-p,l=c>0?1:-1,m=a[0]*eo*l,d=Math.abs(c)>180;if(d^(m>l*p&&l*t>m)){var y=a[1]*eo;y>g&&(g=y)}else if(m=(m+360)%360-180,d^(m>l*p&&l*t>m)){var y=-a[1]*eo;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t)}else n(t,e);v=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,v=null}function i(n,e){if(v){var r=n-p;y+=Math.abs(r)>180?r+(r>0?360:-360):r}else m=n,d=e;zo.point(n,e),t(n,e)}function u(){zo.lineStart()}function a(){i(m,d),zo.lineEnd(),Math.abs(y)>Qa&&(s=-(h=180)),x[0]=s,x[1]=h,v=null}function o(n,t){return(t-=n)<0?t+360:t}function c(n,t){return n[0]-t[0]}function l(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:n<t[0]||t[1]<n}var s,f,h,g,p,m,d,v,y,M,x,b={point:n,lineStart:e,lineEnd:r,polygonStart:function(){b.point=i,b.lineStart=u,b.lineEnd=a,y=0,zo.polygonStart()},polygonEnd:function(){zo.polygonEnd(),b.point=n,b.lineStart=e,b.lineEnd=r,0>Co?(s=-(h=180),f=-(g=90)):y>Qa?g=90:-Qa>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],ya.geo.stream(n,b);var t=M.length;if(t){M.sort(c);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],l(e[0],i)||l(e[1],i)?(o(i[0],e[1])>o(i[0],i[1])&&(i[1]=e[1]),o(e[0],i[1])>o(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var a,e,p=-1/0,t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(a=o(i[1],e[0]))>p&&(p=a,s=e[0],h=i[1])}return M=x=null,1/0===s||1/0===f?[[0/0,0/0],[0/0,0/0]]:[[s,f],[h,g]]}}(),ya.geo.centroid=function(n){Do=jo=Lo=Ho=Fo=Po=Oo=Yo=Ro=Uo=Io=0,ya.geo.stream(n,Vo);var t=Ro,e=Uo,r=Io,i=t*t+e*e+r*r;return no>i&&(t=Po,e=Oo,r=Yo,Qa>jo&&(t=Lo,e=Ho,r=Fo),i=t*t+e*e+r*r,no>i)?[0/0,0/0]:[Math.atan2(e,t)*eo,V(r/Math.sqrt(i))*eo]};var Do,jo,Lo,Ho,Fo,Po,Oo,Yo,Ro,Uo,Io,Vo={sphere:s,point:Yt,lineStart:Ut,lineEnd:It,polygonStart:function(){Vo.lineStart=Vt},polygonEnd:function(){Vo.lineStart=Ut}},Xo=$t(Xt,Qt,te,ee),Zo=[-Ka,0],Bo=1e9;(ya.geo.conicEqualArea=function(){return oe(ce)}).raw=ce,ya.geo.albers=function(){return ya.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ya.geo.albersUsa=function(){function n(n){var u=n[0],a=n[1];return t=null,e(u,a),t||(r(u,a),t)||i(u,a),t}var t,e,r,i,u=ya.geo.albers(),a=ya.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),o=ya.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),c={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?a:i>=.166&&.234>i&&r>=-.214&&-.115>r?o:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=a.stream(n),r=o.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),a.precision(t),o.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),a.scale(.35*t),o.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var l=u.scale(),s=+t[0],f=+t[1];return e=u.translate(t).clipExtent([[s-.455*l,f-.238*l],[s+.455*l,f+.238*l]]).stream(c).point,r=a.translate([s-.307*l,f+.201*l]).clipExtent([[s-.425*l+Qa,f+.12*l+Qa],[s-.214*l-Qa,f+.234*l-Qa]]).stream(c).point,i=o.translate([s-.205*l,f+.212*l]).clipExtent([[s-.214*l+Qa,f+.166*l+Qa],[s-.115*l-Qa,f+.234*l-Qa]]).stream(c).point,n},n.scale(1070)};var $o,Wo,Jo,Go,Ko,Qo,nc={point:s,lineStart:s,lineEnd:s,polygonStart:function(){Wo=0,nc.lineStart=le},polygonEnd:function(){nc.lineStart=nc.lineEnd=nc.point=s,$o+=Math.abs(Wo/2)}},tc={point:se,lineStart:s,lineEnd:s,polygonStart:s,polygonEnd:s},ec={point:ge,lineStart:pe,lineEnd:me,polygonStart:function(){ec.lineStart=de},polygonEnd:function(){ec.point=ge,ec.lineStart=pe,ec.lineEnd=me}};ya.geo.path=function(){function n(n){return n&&("function"==typeof o&&u.pointRadius(+o.apply(this,arguments)),a&&a.valid||(a=i(u)),ya.geo.stream(n,a)),u.result()}function t(){return a=null,n}var e,r,i,u,a,o=4.5;return n.area=function(n){return $o=0,ya.geo.stream(n,i(nc)),$o},n.centroid=function(n){return Lo=Ho=Fo=Po=Oo=Yo=Ro=Uo=Io=0,ya.geo.stream(n,i(ec)),Io?[Ro/Io,Uo/Io]:Yo?[Po/Yo,Oo/Yo]:Fo?[Lo/Fo,Ho/Fo]:[0/0,0/0]},n.bounds=function(n){return Ko=Qo=-(Jo=Go=1/0),ya.geo.stream(n,i(tc)),[[Jo,Go],[Ko,Qo]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||Me(n):mt,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new fe:new ve(n),"function"!=typeof o&&u.pointRadius(o),t()):r},n.pointRadius=function(t){return arguments.length?(o="function"==typeof t?t:(u.pointRadius(+t),+t),n):o},n.projection(ya.geo.albersUsa()).context(null)},ya.geo.projection=xe,ya.geo.projectionMutator=be,(ya.geo.equirectangular=function(){return xe(we)}).raw=we.invert=we,ya.geo.rotation=function(n){function t(t){return t=n(t[0]*to,t[1]*to),t[0]*=eo,t[1]*=eo,t}return n=Se(n[0]%360*to,n[1]*to,n.length>2?n[2]*to:0),t.invert=function(t){return t=n.invert(t[0]*to,t[1]*to),t[0]*=eo,t[1]*=eo,t},t},ya.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=Se(-n[0]*to,-n[1]*to,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=eo,n[1]*=eo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=Ne((t=+r)*to,i*to),n):t},n.precision=function(r){return arguments.length?(e=Ne(t*to,(i=+r)*to),n):i},n.angle(90)},ya.geo.distance=function(n,t){var e,r=(t[0]-n[0])*to,i=n[1]*to,u=t[1]*to,a=Math.sin(r),o=Math.cos(r),c=Math.sin(i),l=Math.cos(i),s=Math.sin(u),f=Math.cos(u);return Math.atan2(Math.sqrt((e=f*a)*e+(e=l*s-c*f*o)*e),c*s+l*f*o)},ya.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ya.range(Math.ceil(u/d)*d,i,d).map(h).concat(ya.range(Math.ceil(l/v)*v,c,v).map(g)).concat(ya.range(Math.ceil(r/p)*p,e,p).filter(function(n){return Math.abs(n%d)>Qa}).map(s)).concat(ya.range(Math.ceil(o/m)*m,a,m).filter(function(n){return Math.abs(n%v)>Qa}).map(f))}var e,r,i,u,a,o,c,l,s,f,h,g,p=10,m=p,d=90,v=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(g(c).slice(1),h(i).reverse().slice(1),g(l).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],l=+t[0][1],c=+t[1][1],u>i&&(t=u,u=i,i=t),l>c&&(t=l,l=c,c=t),n.precision(y)):[[u,l],[i,c]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],o=+t[0][1],a=+t[1][1],r>e&&(t=r,r=e,e=t),o>a&&(t=o,o=a,a=t),n.precision(y)):[[r,o],[e,a]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],v=+t[1],n):[d,v]},n.minorStep=function(t){return arguments.length?(p=+t[0],m=+t[1],n):[p,m]},n.precision=function(t){return arguments.length?(y=+t,s=Te(o,a,90),f=Ce(r,e,y),h=Te(l,c,90),g=Ce(u,i,y),n):y},n.majorExtent([[-180,-90+Qa],[180,90-Qa]]).minorExtent([[-180,-80-Qa],[180,80+Qa]])},ya.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=ze,i=De;return n.distance=function(){return ya.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ya.geo.interpolate=function(n,t){return je(n[0]*to,n[1]*to,t[0]*to,t[1]*to)},ya.geo.length=function(n){return rc=0,ya.geo.stream(n,ic),rc};var rc,ic={sphere:s,point:s,lineStart:Le,lineEnd:s,polygonStart:s,polygonEnd:s},uc=He(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ya.geo.azimuthalEqualArea=function(){return xe(uc)}).raw=uc;var ac=He(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},mt);(ya.geo.azimuthalEquidistant=function(){return xe(ac)}).raw=ac,(ya.geo.conicConformal=function(){return oe(Fe)}).raw=Fe,(ya.geo.conicEquidistant=function(){return oe(Pe)}).raw=Pe;var oc=He(function(n){return 1/n},Math.atan);(ya.geo.gnomonic=function(){return xe(oc)}).raw=oc,Oe.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Ka/2]},(ya.geo.mercator=function(){return Ye(Oe)}).raw=Oe;var cc=He(function(){return 1},Math.asin);(ya.geo.orthographic=function(){return xe(cc)}).raw=cc;var lc=He(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ya.geo.stereographic=function(){return xe(lc)}).raw=lc,Re.invert=function(n,t){return[Math.atan2(X(n),Math.cos(t)),V(Math.sin(t)/Z(n))]},(ya.geo.transverseMercator=function(){return Ye(Re)}).raw=Re,ya.geom={},ya.svg={},ya.svg.line=function(){return Ue(mt)};var sc=ya.map({linear:Xe,"linear-closed":Ze,step:Be,"step-before":$e,"step-after":We,basis:tr,"basis-open":er,"basis-closed":rr,bundle:ir,cardinal:Ke,"cardinal-open":Je,"cardinal-closed":Ge,monotone:sr});sc.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)
+});var fc=[0,2/3,1/3,0],hc=[0,1/3,2/3,0],gc=[0,1/6,2/3,1/6];ya.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i,u,a,o,c,l,s,f,h,g,p,m=pt(e),d=pt(r),v=n.length,y=v-1,M=[],x=[],b=0;if(m===Ie&&r===Ve)t=n;else for(u=0,t=[];v>u;++u)t.push([+m.call(this,i=n[u],u),+d.call(this,i,u)]);for(u=1;v>u;++u)(t[u][1]<t[b][1]||t[u][1]==t[b][1]&&t[u][0]<t[b][0])&&(b=u);for(u=0;v>u;++u)u!==b&&(c=t[u][1]-t[b][1],o=t[u][0]-t[b][0],M.push({angle:Math.atan2(c,o),index:u}));for(M.sort(function(n,t){return n.angle-t.angle}),g=M[0].angle,h=M[0].index,f=0,u=1;y>u;++u){if(a=M[u].index,g==M[u].angle){if(o=t[h][0]-t[b][0],c=t[h][1]-t[b][1],l=t[a][0]-t[b][0],s=t[a][1]-t[b][1],o*o+c*c>=l*l+s*s){M[u].index=-1;continue}M[f].index=-1}g=M[u].angle,f=u,h=a}for(x.push(b),u=0,a=0;2>u;++a)M[a].index>-1&&(x.push(M[a].index),u++);for(p=x.length;y>a;++a)if(!(M[a].index<0)){for(;!fr(x[p-2],x[p-1],M[a].index,t);)--p;x[p++]=M[a].index}var _=[];for(u=p-1;u>=0;--u)_.push(n[x[u]]);return _}var e=Ie,r=Ve;return arguments.length?t(n):(t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t)},ya.geom.polygon=function(n){return La(n,pc),n};var pc=ya.geom.polygon.prototype=[];pc.area=function(){for(var n,t=-1,e=this.length,r=this[e-1],i=0;++t<e;)n=r,r=this[t],i+=n[1]*r[0]-n[0]*r[1];return.5*i},pc.centroid=function(n){var t,e,r=-1,i=this.length,u=0,a=0,o=this[i-1];for(arguments.length||(n=-1/(6*this.area()));++r<i;)t=o,o=this[r],e=t[0]*o[1]-o[0]*t[1],u+=(t[0]+o[0])*e,a+=(t[1]+o[1])*e;return[u*n,a*n]},pc.clip=function(n){for(var t,e,r,i,u,a,o=pr(n),c=-1,l=this.length-pr(this),s=this[l-1];++c<l;){for(t=n.slice(),n.length=0,i=this[c],u=t[(r=t.length-o)-1],e=-1;++e<r;)a=t[e],hr(a,s,i)?(hr(u,s,i)||n.push(gr(u,a,s,i)),n.push(a)):hr(u,s,i)&&n.push(gr(u,a,s,i)),u=a;o&&n.push(n[0]),s=i}return n},ya.geom.delaunay=function(n){var t=n.map(function(){return[]}),e=[];return mr(n,function(e){t[e.region.l.index].push(n[e.region.r.index])}),t.forEach(function(t,r){var i=n[r],u=i[0],a=i[1];t.forEach(function(n){n.angle=Math.atan2(n[0]-u,n[1]-a)}),t.sort(function(n,t){return n.angle-t.angle});for(var o=0,c=t.length-1;c>o;o++)e.push([i,t[o],t[o+1]])}),e},ya.geom.voronoi=function(n){function t(n){var t,u,a,o=n.map(function(){return[]}),c=pt(e),l=pt(r),s=n.length,f=1e6;if(c===Ie&&l===Ve)t=n;else for(t=new Array(s),a=0;s>a;++a)t[a]=[+c.call(this,u=n[a],a),+l.call(this,u,a)];if(mr(t,function(n){var t,e,r,i,u,a;1===n.a&&n.b>=0?(t=n.ep.r,e=n.ep.l):(t=n.ep.l,e=n.ep.r),1===n.a?(u=t?t.y:-f,r=n.c-n.b*u,a=e?e.y:f,i=n.c-n.b*a):(r=t?t.x:-f,u=n.c-n.a*r,i=e?e.x:f,a=n.c-n.a*i);var c=[r,u],l=[i,a];o[n.region.l.index].push(c,l),o[n.region.r.index].push(c,l)}),o=o.map(function(n,e){var r=t[e][0],i=t[e][1],u=n.map(function(n){return Math.atan2(n[0]-r,n[1]-i)}),a=ya.range(n.length).sort(function(n,t){return u[n]-u[t]});return a.filter(function(n,t){return!t||u[n]-u[a[t-1]]>Qa}).map(function(t){return n[t]})}),o.forEach(function(n,e){var r=n.length;if(!r)return n.push([-f,-f],[-f,f],[f,f],[f,-f]);if(!(r>2)){var i=t[e],u=n[0],a=n[1],o=i[0],c=i[1],l=u[0],s=u[1],h=a[0],g=a[1],p=Math.abs(h-l),m=g-s;if(Math.abs(m)<Qa){var d=s>c?-f:f;n.push([-f,d],[f,d])}else if(Qa>p){var v=l>o?-f:f;n.push([v,-f],[v,f])}else{var d=(l-o)*(g-s)>(h-l)*(s-c)?f:-f,y=Math.abs(m)-p;Math.abs(y)<Qa?n.push([0>m?d:-d,d]):(y>0&&(d*=-1),n.push([-f,d],[f,d]))}}}),i)for(a=0;s>a;++a)i.clip(o[a]);for(a=0;s>a;++a)o[a].point=n[a];return o}var e=Ie,r=Ve,i=null;return arguments.length?t(n):(t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t.clipExtent=function(n){if(!arguments.length)return i&&[i[0],i[2]];if(null==n)i=null;else{var e=+n[0][0],r=+n[0][1],u=+n[1][0],a=+n[1][1];i=ya.geom.polygon([[e,r],[e,a],[u,a],[u,r]])}return t},t.size=function(n){return arguments.length?t.clipExtent(n&&[[0,0],n]):i&&i[2]},t.links=function(n){var t,i,u,a=n.map(function(){return[]}),o=[],c=pt(e),l=pt(r),s=n.length;if(c===Ie&&l===Ve)t=n;else for(t=new Array(s),u=0;s>u;++u)t[u]=[+c.call(this,i=n[u],u),+l.call(this,i,u)];return mr(t,function(t){var e=t.region.l.index,r=t.region.r.index;a[e][r]||(a[e][r]=a[r][e]=!0,o.push({source:n[e],target:n[r]}))}),o},t.triangles=function(n){if(e===Ie&&r===Ve)return ya.geom.delaunay(n);for(var t,i=new Array(c),u=pt(e),a=pt(r),o=-1,c=n.length;++o<c;)(i[o]=[+u.call(this,t=n[o],o),+a.call(this,t,o)]).data=t;return ya.geom.delaunay(i).map(function(n){return n.map(function(n){return n.data})})},t)};var mc={l:"r",r:"l"};ya.geom.quadtree=function(n,t,e,r,i){function u(n){function u(n,t,e,r,i,u,a,o){if(!isNaN(e)&&!isNaN(r))if(n.leaf){var c=n.x,s=n.y;if(null!=c)if(Math.abs(c-e)+Math.abs(s-r)<.01)l(n,t,e,r,i,u,a,o);else{var f=n.point;n.x=n.y=n.point=null,l(n,f,c,s,i,u,a,o),l(n,t,e,r,i,u,a,o)}else n.x=e,n.y=r,n.point=t}else l(n,t,e,r,i,u,a,o)}function l(n,t,e,r,i,a,o,c){var l=.5*(i+o),s=.5*(a+c),f=e>=l,h=r>=s,g=(h<<1)+f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=yr()),f?i=l:o=l,h?a=s:c=s,u(n,t,e,r,i,a,o,c)}var s,f,h,g,p,m,d,v,y,M=pt(o),x=pt(c);if(null!=t)m=t,d=e,v=r,y=i;else if(v=y=-(m=d=1/0),f=[],h=[],p=n.length,a)for(g=0;p>g;++g)s=n[g],s.x<m&&(m=s.x),s.y<d&&(d=s.y),s.x>v&&(v=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);m>b&&(m=b),d>_&&(d=_),b>v&&(v=b),_>y&&(y=_),f.push(b),h.push(_)}var w=v-m,S=y-d;w>S?y=d+w:v=m+S;var E=yr();if(E.add=function(n){u(E,n,+M(n,++g),+x(n,g),m,d,v,y)},E.visit=function(n){Mr(n,E,m,d,v,y)},g=-1,null==t){for(;++g<p;)u(E,n[g],f[g],h[g],m,d,v,y);--g}else n.forEach(E.add);return f=h=n=s=null,E}var a,o=Ie,c=Ve;return(a=arguments.length)?(o=dr,c=vr,3===a&&(i=e,r=t,e=t=0),u(n)):(u.x=function(n){return arguments.length?(o=n,u):o},u.y=function(n){return arguments.length?(c=n,u):c},u.extent=function(n){return arguments.length?(null==n?t=e=r=i=null:(t=+n[0][0],e=+n[0][1],r=+n[1][0],i=+n[1][1]),u):null==t?null:[[t,e],[r,i]]},u.size=function(n){return arguments.length?(null==n?t=e=r=i=null:(t=e=0,r=+n[0],i=+n[1]),u):null==t?null:[r-t,i-e]},u)},ya.interpolateRgb=xr,ya.interpolateObject=br,ya.interpolateNumber=_r,ya.interpolateString=wr;var dc=/[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g;ya.interpolate=Sr,ya.interpolators=[function(n,t){var e=typeof t;return("string"===e?so.has(t)||/^(#|rgb\(|hsl\()/.test(t)?xr:wr:t instanceof P?xr:"object"===e?Array.isArray(t)?Er:br:_r)(n,t)}],ya.interpolateArray=Er;var vc=function(){return mt},yc=ya.map({linear:vc,poly:zr,quad:function(){return qr},cubic:function(){return Tr},sin:function(){return Dr},exp:function(){return jr},circle:function(){return Lr},elastic:Hr,back:Fr,bounce:function(){return Pr}}),Mc=ya.map({"in":mt,out:Ar,"in-out":Nr,"out-in":function(n){return Nr(Ar(n))}});ya.ease=function(n){var t=n.indexOf("-"),e=t>=0?n.substring(0,t):n,r=t>=0?n.substring(t+1):"in";return e=yc.get(e)||vc,r=Mc.get(r)||mt,kr(r(e.apply(null,Array.prototype.slice.call(arguments,1))))},ya.interpolateHcl=Or,ya.interpolateHsl=Yr,ya.interpolateLab=Rr,ya.interpolateRound=Ur,ya.transform=function(n){var t=Ma.createElementNS(ya.ns.prefix.svg,"g");return(ya.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Ir(e?e.matrix:xc)})(n)},Ir.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var xc={a:1,b:0,c:0,d:1,e:0,f:0};ya.interpolateTransform=Br,ya.layout={},ya.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++e<r;)t.push(Jr(n[e]));return t}},ya.layout.chord=function(){function n(){var n,l,f,h,g,p={},m=[],d=ya.range(u),v=[];for(e=[],r=[],n=0,h=-1;++h<u;){for(l=0,g=-1;++g<u;)l+=i[h][g];m.push(l),v.push(ya.range(u)),n+=l}for(a&&d.sort(function(n,t){return a(m[n],m[t])}),o&&v.forEach(function(n,t){n.sort(function(n,e){return o(i[t][n],i[t][e])})}),n=(2*Ka-s*u)/n,l=0,h=-1;++h<u;){for(f=l,g=-1;++g<u;){var y=d[h],M=v[y][g],x=i[y][M],b=l,_=l+=x*n;p[y+"-"+M]={index:y,subindex:M,startAngle:b,endAngle:_,value:x}}r[y]={index:y,startAngle:f,endAngle:l,value:(l-f)/n},l+=s}for(h=-1;++h<u;)for(g=h-1;++g<u;){var w=p[h+"-"+g],S=p[g+"-"+h];(w.value||S.value)&&e.push(w.value<S.value?{source:S,target:w}:{source:w,target:S})}c&&t()}function t(){e.sort(function(n,t){return c((n.source.value+n.target.value)/2,(t.source.value+t.target.value)/2)})}var e,r,i,u,a,o,c,l={},s=0;return l.matrix=function(n){return arguments.length?(u=(i=n)&&i.length,e=r=null,l):i},l.padding=function(n){return arguments.length?(s=n,e=r=null,l):s},l.sortGroups=function(n){return arguments.length?(a=n,e=r=null,l):a},l.sortSubgroups=function(n){return arguments.length?(o=n,e=null,l):o},l.sortChords=function(n){return arguments.length?(c=n,e&&t(),l):c},l.chords=function(){return e||n(),e},l.groups=function(){return r||n(),r},l},ya.layout.force=function(){function n(n){return function(t,e,r,i){if(t.point!==n){var u=t.cx-n.x,a=t.cy-n.y,o=1/Math.sqrt(u*u+a*a);if(m>(i-e)*o){var c=t.charge*o*o;return n.px-=u*c,n.py-=a*c,!0}if(t.point&&isFinite(o)){var c=t.pointCharge*o*o;n.px-=u*c,n.py-=a*c}}return!t.charge}}function t(n){n.px=ya.event.x,n.py=ya.event.y,o.resume()}var e,r,i,u,a,o={},c=ya.dispatch("start","tick","end"),l=[1,1],s=.9,f=bc,h=_c,g=-30,p=.1,m=.8,d=[],v=[];return o.tick=function(){if((r*=.99)<.005)return c.end({type:"end",alpha:r=0}),!0;var t,e,o,f,h,m,y,M,x,b=d.length,_=v.length;for(e=0;_>e;++e)o=v[e],f=o.source,h=o.target,M=h.x-f.x,x=h.y-f.y,(m=M*M+x*x)&&(m=r*u[e]*((m=Math.sqrt(m))-i[e])/m,M*=m,x*=m,h.x-=M*(y=f.weight/(h.weight+f.weight)),h.y-=x*y,f.x+=M*(y=1-y),f.y+=x*y);if((y=r*p)&&(M=l[0]/2,x=l[1]/2,e=-1,y))for(;++e<b;)o=d[e],o.x+=(M-o.x)*y,o.y+=(x-o.y)*y;if(g)for(ri(t=ya.geom.quadtree(d),r,a),e=-1;++e<b;)(o=d[e]).fixed||t.visit(n(o));for(e=-1;++e<b;)o=d[e],o.fixed?(o.x=o.px,o.y=o.py):(o.x-=(o.px-(o.px=o.x))*s,o.y-=(o.py-(o.py=o.y))*s);c.tick({type:"tick",alpha:r})},o.nodes=function(n){return arguments.length?(d=n,o):d},o.links=function(n){return arguments.length?(v=n,o):v},o.size=function(n){return arguments.length?(l=n,o):l},o.linkDistance=function(n){return arguments.length?(f="function"==typeof n?n:+n,o):f},o.distance=o.linkDistance,o.linkStrength=function(n){return arguments.length?(h="function"==typeof n?n:+n,o):h},o.friction=function(n){return arguments.length?(s=+n,o):s},o.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,o):g},o.gravity=function(n){return arguments.length?(p=+n,o):p},o.theta=function(n){return arguments.length?(m=+n,o):m},o.alpha=function(n){return arguments.length?(n=+n,r?r=n>0?n:0:n>0&&(c.start({type:"start",alpha:r=n}),ya.timer(o.tick)),o):r},o.start=function(){function n(n,r){for(var i,u=t(e),a=-1,o=u.length;++a<o;)if(!isNaN(i=u[a][n]))return i;return Math.random()*r}function t(){if(!c){for(c=[],r=0;p>r;++r)c[r]=[];for(r=0;m>r;++r){var n=v[r];c[n.source.index].push(n.target),c[n.target.index].push(n.source)}}return c[e]}var e,r,c,s,p=d.length,m=v.length,y=l[0],M=l[1];for(e=0;p>e;++e)(s=d[e]).index=e,s.weight=0;for(e=0;m>e;++e)s=v[e],"number"==typeof s.source&&(s.source=d[s.source]),"number"==typeof s.target&&(s.target=d[s.target]),++s.source.weight,++s.target.weight;for(e=0;p>e;++e)s=d[e],isNaN(s.x)&&(s.x=n("x",y)),isNaN(s.y)&&(s.y=n("y",M)),isNaN(s.px)&&(s.px=s.x),isNaN(s.py)&&(s.py=s.y);if(i=[],"function"==typeof f)for(e=0;m>e;++e)i[e]=+f.call(this,v[e],e);else for(e=0;m>e;++e)i[e]=f;if(u=[],"function"==typeof h)for(e=0;m>e;++e)u[e]=+h.call(this,v[e],e);else for(e=0;m>e;++e)u[e]=h;if(a=[],"function"==typeof g)for(e=0;p>e;++e)a[e]=+g.call(this,d[e],e);else for(e=0;p>e;++e)a[e]=g;return o.resume()},o.resume=function(){return o.alpha(.1)},o.stop=function(){return o.alpha(0)},o.drag=function(){return e||(e=ya.behavior.drag().origin(mt).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?(this.on("mouseover.force",ti).on("mouseout.force",ei).call(e),void 0):e},ya.rebind(o,c,"on")};var bc=20,_c=1;ya.layout.hierarchy=function(){function n(t,a,o){var c=i.call(e,t,a);if(t.depth=a,o.push(t),c&&(l=c.length)){for(var l,s,f=-1,h=t.children=[],g=0,p=a+1;++f<l;)s=n(c[f],p,o),s.parent=t,h.push(s),g+=s.value;r&&h.sort(r),u&&(t.value=g)}else u&&(t.value=+u.call(e,t,a)||0);return t}function t(n,r){var i=n.children,a=0;if(i&&(o=i.length))for(var o,c=-1,l=r+1;++c<o;)a+=t(i[c],l);else u&&(a=+u.call(e,n,r)||0);return u&&(n.value=a),a}function e(t){var e=[];return n(t,0,e),e}var r=oi,i=ui,u=ai;return e.sort=function(n){return arguments.length?(r=n,e):r},e.children=function(n){return arguments.length?(i=n,e):i},e.value=function(n){return arguments.length?(u=n,e):u},e.revalue=function(n){return t(n,0),n},e},ya.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(a=u.length)){var a,o,c,l=-1;for(r=t.value?r/t.value:0;++l<a;)n(o=u[l],e,c=o.value*r,i),e+=c}}function t(n){var e=n.children,r=0;if(e&&(i=e.length))for(var i,u=-1;++u<i;)r=Math.max(r,t(e[u]));return 1+r}function e(e,u){var a=r.call(this,e,u);return n(a[0],0,i[0],i[1]/t(a[0])),a}var r=ya.layout.hierarchy(),i=[1,1];return e.size=function(n){return arguments.length?(i=n,e):i},ii(e,r)},ya.layout.pie=function(){function n(u){var a=u.map(function(e,r){return+t.call(n,e,r)}),o=+("function"==typeof r?r.apply(this,arguments):r),c=(("function"==typeof i?i.apply(this,arguments):i)-o)/ya.sum(a),l=ya.range(u.length);null!=e&&l.sort(e===wc?function(n,t){return a[t]-a[n]}:function(n,t){return e(u[n],u[t])});var s=[];return l.forEach(function(n){var t;s[n]={data:u[n],value:t=a[n],startAngle:o,endAngle:o+=t*c}}),s}var t=Number,e=wc,r=0,i=2*Ka;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n};var wc={};ya.layout.stack=function(){function n(o,c){var l=o.map(function(e,r){return t.call(n,e,r)}),s=l.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),a.call(n,t,e)]})}),f=e.call(n,s,c);l=ya.permute(l,f),s=ya.permute(s,f);var h,g,p,m=r.call(n,s,c),d=l.length,v=l[0].length;for(g=0;v>g;++g)for(i.call(n,l[0][g],p=m[g],s[0][g][1]),h=1;d>h;++h)i.call(n,l[h][g],p+=s[h-1][g][1],s[h][g][1]);return o}var t=mt,e=hi,r=gi,i=fi,u=li,a=si;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:Sc.get(t)||hi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:Ec.get(t)||gi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(a=t,n):a},n.out=function(t){return arguments.length?(i=t,n):i},n};var Sc=ya.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(pi),u=n.map(mi),a=ya.range(r).sort(function(n,t){return i[n]-i[t]}),o=0,c=0,l=[],s=[];for(t=0;r>t;++t)e=a[t],c>o?(o+=u[e],l.push(e)):(c+=u[e],s.push(e));return s.reverse().concat(l)},reverse:function(n){return ya.range(n.length).reverse()},"default":hi}),Ec=ya.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,a=[],o=0,c=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>o&&(o=r),a.push(r)}for(e=0;u>e;++e)c[e]=(o-a[e])/2;return c},wiggle:function(n){var t,e,r,i,u,a,o,c,l,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=c=l=0,e=1;h>e;++e){for(t=0,i=0;s>t;++t)i+=n[t][e][1];for(t=0,u=0,o=f[e][0]-f[e-1][0];s>t;++t){for(r=0,a=(n[t][e][1]-n[t][e-1][1])/(2*o);t>r;++r)a+=(n[r][e][1]-n[r][e-1][1])/o;u+=a*n[t][e][1]}g[e]=c-=i?u/i*o:0,l>c&&(l=c)}for(e=0;h>e;++e)g[e]-=l;return g},expand:function(n){var t,e,r,i=n.length,u=n[0].length,a=1/i,o=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=a}for(e=0;u>e;++e)o[e]=0;return o},zero:gi});ya.layout.histogram=function(){function n(n,u){for(var a,o,c=[],l=n.map(e,this),s=r.call(this,l,u),f=i.call(this,s,l,u),u=-1,h=l.length,g=f.length-1,p=t?1:1/h;++u<g;)a=c[u]=[],a.dx=f[u+1]-(a.x=f[u]),a.y=0;if(g>0)for(u=-1;++u<h;)o=l[u],o>=s[0]&&o<=s[1]&&(a=c[ya.bisect(f,o,1,g)-1],a.y+=p,a.push(n[u]));return c}var t=!0,e=Number,r=Mi,i=vi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=pt(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return yi(n,t)}:pt(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ya.layout.tree=function(){function n(n,u){function a(n,t){var r=n.children,i=n._tree;if(r&&(u=r.length)){for(var u,o,l,s=r[0],f=s,h=-1;++h<u;)l=r[h],a(l,o),f=c(l,o,f),o=l;Ni(n);var g=.5*(s._tree.prelim+l._tree.prelim);t?(i.prelim=t._tree.prelim+e(n,t),i.mod=i.prelim-g):i.prelim=g}else t&&(i.prelim=t._tree.prelim+e(n,t))}function o(n,t){n.x=n._tree.prelim+t;var e=n.children;if(e&&(r=e.length)){var r,i=-1;for(t+=n._tree.mod;++i<r;)o(e[i],t)}}function c(n,t,r){if(t){for(var i,u=n,a=n,o=t,c=n.parent.children[0],l=u._tree.mod,s=a._tree.mod,f=o._tree.mod,h=c._tree.mod;o=_i(o),u=bi(u),o&&u;)c=bi(c),a=_i(a),a._tree.ancestor=n,i=o._tree.prelim+f-u._tree.prelim-l+e(o,u),i>0&&(qi(Ti(o,n,r),n,i),l+=i,s+=i),f+=o._tree.mod,l+=u._tree.mod,h+=c._tree.mod,s+=a._tree.mod;o&&!_i(a)&&(a._tree.thread=o,a._tree.mod+=f-s),u&&!bi(c)&&(c._tree.thread=u,c._tree.mod+=l-h,r=n)}return r}var l=t.call(this,n,u),s=l[0];Ai(s,function(n,t){n._tree={ancestor:n,prelim:0,mod:0,change:0,shift:0,number:t?t._tree.number+1:0}}),a(s),o(s,-s._tree.prelim);var f=wi(s,Ei),h=wi(s,Si),g=wi(s,ki),p=f.x-e(f,h)/2,m=h.x+e(h,f)/2,d=g.depth||1;return Ai(s,i?function(n){n.x*=r[0],n.y=n.depth*r[1],delete n._tree}:function(n){n.x=(n.x-p)/(m-p)*r[0],n.y=n.depth/d*r[1],delete n._tree}),l}var t=ya.layout.hierarchy().sort(null).value(null),e=xi,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ya.layout.pack=function(){function n(n,u){var a=e.call(this,n,u),o=a[0],c=i[0],l=i[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(o.x=o.y=0,Ai(o,function(n){n.r=+s(n.value)}),Ai(o,Li),r){var f=r*(t?1:Math.max(2*o.r/c,2*o.r/l))/2;Ai(o,function(n){n.r+=f}),Ai(o,Li),Ai(o,function(n){n.r-=f})}return Pi(o,c/2,l/2,t?1:1/Math.max(2*o.r/c,2*o.r/l)),a}var t,e=ya.layout.hierarchy().sort(Ci),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ya.layout.cluster=function(){function n(n,u){var a,o=t.call(this,n,u),c=o[0],l=0;Ai(c,function(n){var t=n.children;t&&t.length?(n.x=Ri(t),n.y=Yi(t)):(n.x=a?l+=e(n,a):0,n.y=0,a=n)});var s=Ui(c),f=Ii(c),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return Ai(c,i?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),o}var t=ya.layout.hierarchy().sort(null).value(null),e=xi,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ya.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++i<u;)r=(e=n[i]).value*(0>t?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var a,o,c,l=f(e),s=[],h=u.slice(),p=1/0,m="slice"===g?l.dx:"dice"===g?l.dy:"slice-dice"===g?1&e.depth?l.dy:l.dx:Math.min(l.dx,l.dy);for(n(h,l.dx*l.dy/e.value),s.area=0;(c=h.length)>0;)s.push(a=h[c-1]),s.area+=a.area,"squarify"!==g||(o=r(s,m))<=p?(h.pop(),p=o):(s.area-=s.pop().area,i(s,m,l,!1),m=Math.min(l.dx,l.dy),s.length=s.area=0,p=1/0);s.length&&(i(s,m,l,!0),s.length=s.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,a=f(t),o=r.slice(),c=[];for(n(o,a.dx*a.dy/t.value),c.area=0;u=o.pop();)c.push(u),c.area+=u.area,null!=u.z&&(i(c,u.z?a.dx:a.dy,a,!o.length),c.length=c.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,a=-1,o=n.length;++a<o;)(e=n[a].area)&&(u>e&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*p/r,r/(t*u*p)):1/0}function i(n,t,e,r){var i,u=-1,a=n.length,o=e.x,l=e.y,s=t?c(n.area/t):0;if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++u<a;)i=n[u],i.x=o,i.y=l,i.dy=s,o+=i.dx=Math.min(e.x+e.dx-o,s?c(i.area/s):0);i.z=!0,i.dx+=e.x+e.dx-o,e.y+=s,e.dy-=s}else{for((r||s>e.dx)&&(s=e.dx);++u<a;)i=n[u],i.x=o,i.y=l,i.dx=s,l+=i.dy=Math.min(e.y+e.dy-l,s?c(i.area/s):0);i.z=!1,i.dy+=e.y+e.dy-l,e.x+=s,e.dx-=s}}function u(r){var i=a||o(r),u=i[0];return u.x=0,u.y=0,u.dx=l[0],u.dy=l[1],a&&o.revalue(u),n([u],u.dx*u.dy/u.value),(a?e:t)(u),h&&(a=i),i}var a,o=ya.layout.hierarchy(),c=Math.round,l=[1,1],s=null,f=Vi,h=!1,g="squarify",p=.5*(1+Math.sqrt(5));return u.size=function(n){return arguments.length?(l=n,u):l},u.padding=function(n){function t(t){var e=n.call(u,t,t.depth);return null==e?Vi(t):Xi(t,"number"==typeof e?[e,e,e,e]:e)}function e(t){return Xi(t,n)}if(!arguments.length)return s;var r;return f=null==(s=n)?Vi:"function"==(r=typeof n)?t:"number"===r?(n=[n,n,n,n],e):e,u},u.round=function(n){return arguments.length?(c=n?Math.round:Number,u):c!=Number},u.sticky=function(n){return arguments.length?(h=n,a=null,u):h},u.ratio=function(n){return arguments.length?(p=n,u):p},u.mode=function(n){return arguments.length?(g=n+"",u):g},ii(u,o)},ya.random={normal:function(n,t){var e=arguments.length;return 2>e&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ya.random.normal.apply(ya,arguments);return function(){return Math.exp(n())}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t/n}}},ya.scale={};var kc={floor:mt,ceil:mt};ya.scale.linear=function(){return Ki([0,1],[0,1],Sr,!1)},ya.scale.log=function(){return uu(ya.scale.linear().domain([0,1]),10,!0,[1,10])};var Ac=ya.format(".0e"),Nc={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ya.scale.pow=function(){return au(ya.scale.linear(),1,[0,1])},ya.scale.sqrt=function(){return ya.scale.pow().exponent(.5)},ya.scale.ordinal=function(){return cu([],{t:"range",a:[[]]})},ya.scale.category10=function(){return ya.scale.ordinal().range(qc)},ya.scale.category20=function(){return ya.scale.ordinal().range(Tc)},ya.scale.category20b=function(){return ya.scale.ordinal().range(Cc)},ya.scale.category20c=function(){return ya.scale.ordinal().range(zc)};var qc=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(ut),Tc=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(ut),Cc=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(ut),zc=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(ut);ya.scale.quantile=function(){return lu([],[])},ya.scale.quantize=function(){return su(0,1,[0,1])},ya.scale.threshold=function(){return fu([.5],[0,1])},ya.scale.identity=function(){return hu([0,1])},ya.svg.arc=function(){function n(){var n=t.apply(this,arguments),u=e.apply(this,arguments),a=r.apply(this,arguments)+Dc,o=i.apply(this,arguments)+Dc,c=(a>o&&(c=a,a=o,o=c),o-a),l=Ka>c?"0":"1",s=Math.cos(a),f=Math.sin(a),h=Math.cos(o),g=Math.sin(o);return c>=jc?n?"M0,"+u+"A"+u+","+u+" 0 1,1 0,"+-u+"A"+u+","+u+" 0 1,1 0,"+u+"M0,"+n+"A"+n+","+n+" 0 1,0 0,"+-n+"A"+n+","+n+" 0 1,0 0,"+n+"Z":"M0,"+u+"A"+u+","+u+" 0 1,1 0,"+-u+"A"+u+","+u+" 0 1,1 0,"+u+"Z":n?"M"+u*s+","+u*f+"A"+u+","+u+" 0 "+l+",1 "+u*h+","+u*g+"L"+n*h+","+n*g+"A"+n+","+n+" 0 "+l+",0 "+n*s+","+n*f+"Z":"M"+u*s+","+u*f+"A"+u+","+u+" 0 "+l+",1 "+u*h+","+u*g+"L0,0"+"Z"}var t=gu,e=pu,r=mu,i=du;return n.innerRadius=function(e){return arguments.length?(t=pt(e),n):t},n.outerRadius=function(t){return arguments.length?(e=pt(t),n):e},n.startAngle=function(t){return arguments.length?(r=pt(t),n):r},n.endAngle=function(t){return arguments.length?(i=pt(t),n):i},n.centroid=function(){var n=(t.apply(this,arguments)+e.apply(this,arguments))/2,u=(r.apply(this,arguments)+i.apply(this,arguments))/2+Dc;return[Math.cos(u)*n,Math.sin(u)*n]},n};var Dc=-Ka/2,jc=2*Ka-1e-6;ya.svg.line.radial=function(){var n=Ue(vu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},$e.reverse=We,We.reverse=$e,ya.svg.area=function(){return yu(mt)},ya.svg.area.radial=function(){var n=yu(vu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ya.svg.chord=function(){function n(n,o){var c=t(this,u,n,o),l=t(this,a,n,o);return"M"+c.p0+r(c.r,c.p1,c.a1-c.a0)+(e(c,l)?i(c.r,c.p1,c.r,c.p0):i(c.r,c.p1,l.r,l.p0)+r(l.r,l.p1,l.a1-l.a0)+i(l.r,l.p1,c.r,c.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=o.call(n,i,r),a=c.call(n,i,r)+Dc,s=l.call(n,i,r)+Dc;return{r:u,a0:a,a1:s,p0:[u*Math.cos(a),u*Math.sin(a)],p1:[u*Math.cos(s),u*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Ka)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=ze,a=De,o=Mu,c=mu,l=du;return n.radius=function(t){return arguments.length?(o=pt(t),n):o},n.source=function(t){return arguments.length?(u=pt(t),n):u},n.target=function(t){return arguments.length?(a=pt(t),n):a},n.startAngle=function(t){return arguments.length?(c=pt(t),n):c},n.endAngle=function(t){return arguments.length?(l=pt(t),n):l},n},ya.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),a=e.call(this,n,i),o=(u.y+a.y)/2,c=[u,{x:u.x,y:o},{x:a.x,y:o},a];return c=c.map(r),"M"+c[0]+"C"+c[1]+" "+c[2]+" "+c[3]}var t=ze,e=De,r=xu;return n.source=function(e){return arguments.length?(t=pt(e),n):t},n.target=function(t){return arguments.length?(e=pt(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ya.svg.diagonal.radial=function(){var n=ya.svg.diagonal(),t=xu,e=n.projection;return n.projection=function(n){return arguments.length?e(bu(t=n)):t},n},ya.svg.symbol=function(){function n(n,r){return(Lc.get(t.call(this,n,r))||Su)(e.call(this,n,r))}var t=wu,e=_u;return n.type=function(e){return arguments.length?(t=pt(e),n):t},n.size=function(t){return arguments.length?(e=pt(t),n):e},n};var Lc=ya.map({circle:Su,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Oc)),e=t*Oc;return"M0,"+-t+"L"+e+",0"+" 0,"+t+" "+-e+",0"+"Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/Pc),e=t*Pc/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/Pc),e=t*Pc/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ya.svg.symbolTypes=Lc.keys();var Hc,Fc,Pc=Math.sqrt(3),Oc=Math.tan(30*to),Yc=[],Rc=0;Yc.call=Ya.call,Yc.empty=Ya.empty,Yc.node=Ya.node,Yc.size=Ya.size,ya.transition=function(n){return arguments.length?Hc?n.transition():n:Ia.transition()},ya.transition.prototype=Yc,Yc.select=function(n){var t,e,r,i=this.id,u=[];n=v(n);for(var a=-1,o=this.length;++a<o;){u.push(t=[]);for(var c=this[a],l=-1,s=c.length;++l<s;)(r=c[l])&&(e=n.call(r,r.__data__,l,a))?("__data__"in r&&(e.__data__=r.__data__),Nu(e,l,i,r.__transition__[i]),t.push(e)):t.push(null)}return Eu(u,i)},Yc.selectAll=function(n){var t,e,r,i,u,a=this.id,o=[];n=y(n);for(var c=-1,l=this.length;++c<l;)for(var s=this[c],f=-1,h=s.length;++f<h;)if(r=s[f]){u=r.__transition__[a],e=n.call(r,r.__data__,f,c),o.push(t=[]);for(var g=-1,p=e.length;++g<p;)(i=e[g])&&Nu(i,g,a,u),t.push(i)}return Eu(o,a)},Yc.filter=function(n){var t,e,r,i=[];"function"!=typeof n&&(n=N(n));for(var u=0,a=this.length;a>u;u++){i.push(t=[]);for(var e=this[u],o=0,c=e.length;c>o;o++)(r=e[o])&&n.call(r,r.__data__,o)&&t.push(r)}return Eu(i,this.id)},Yc.tween=function(n,t){var e=this.id;return arguments.length<2?this.node().__transition__[e].tween.get(n):T(this,null==t?function(t){t.__transition__[e].tween.remove(n)}:function(r){r.__transition__[e].tween.set(n,t)})},Yc.attr=function(n,t){function e(){this.removeAttribute(o)}function r(){this.removeAttributeNS(o.space,o.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(o);return e!==n&&(t=a(e,n),function(n){this.setAttribute(o,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(o.space,o.local);return e!==n&&(t=a(e,n),function(n){this.setAttributeNS(o.space,o.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var a="transform"==n?Br:Sr,o=ya.ns.qualify(n);return ku(this,"attr."+n,t,o.local?u:i)},Yc.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ya.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yc.style=function(n,t,e){function r(){this.style.removeProperty(n)}function i(t){return null==t?r:(t+="",function(){var r,i=ba.getComputedStyle(this,null).getPropertyValue(n);return i!==t&&(r=Sr(i,t),function(t){this.style.setProperty(n,r(t),e)})})}var u=arguments.length;if(3>u){if("string"!=typeof n){2>u&&(t="");for(e in n)this.style(e,n[e],t);return this}e=""}return ku(this,"style."+n,t,i)},Yc.styleTween=function(n,t,e){function r(r,i){var u=t.call(this,r,i,ba.getComputedStyle(this,null).getPropertyValue(n));return u&&function(t){this.style.setProperty(n,u(t),e)}}return arguments.length<3&&(e=""),this.tween("style."+n,r)},Yc.text=function(n){return ku(this,"text",n,Au)},Yc.remove=function(){return this.each("end.transition",function(){var n;!this.__transition__&&(n=this.parentNode)&&n.removeChild(this)})},Yc.ease=function(n){var t=this.id;return arguments.length<1?this.node().__transition__[t].ease:("function"!=typeof n&&(n=ya.ease.apply(ya,arguments)),T(this,function(e){e.__transition__[t].ease=n}))},Yc.delay=function(n){var t=this.id;return T(this,"function"==typeof n?function(e,r,i){e.__transition__[t].delay=0|n.call(e,e.__data__,r,i)}:(n|=0,function(e){e.__transition__[t].delay=n}))},Yc.duration=function(n){var t=this.id;return T(this,"function"==typeof n?function(e,r,i){e.__transition__[t].duration=Math.max(1,0|n.call(e,e.__data__,r,i))}:(n=Math.max(1,0|n),function(e){e.__transition__[t].duration=n}))},Yc.each=function(n,t){var e=this.id;if(arguments.length<2){var r=Fc,i=Hc;Hc=e,T(this,function(t,r,i){Fc=t.__transition__[e],n.call(t,t.__data__,r,i)}),Fc=r,Hc=i}else T(this,function(r){var i=r.__transition__[e];(i.event||(i.event=ya.dispatch("start","end"))).on(n,t)});return this},Yc.transition=function(){for(var n,t,e,r,i=this.id,u=++Rc,a=[],o=0,c=this.length;c>o;o++){a.push(n=[]);for(var t=this[o],l=0,s=t.length;s>l;l++)(e=t[l])&&(r=Object.create(e.__transition__[i]),r.delay+=r.duration,Nu(e,l,u,r)),n.push(e)}return Eu(a,u)},ya.svg.axis=function(){function n(n){n.each(function(){var n,f=ya.select(this),h=null==l?e.ticks?e.ticks.apply(e,c):e.domain():l,g=null==t?e.tickFormat?e.tickFormat.apply(e,c):String:t,p=Cu(e,h,s),m=f.selectAll(".tick.minor").data(p,String),d=m.enter().insert("line",".tick").attr("class","tick minor").style("opacity",1e-6),v=ya.transition(m.exit()).style("opacity",1e-6).remove(),y=ya.transition(m).style("opacity",1),M=f.selectAll(".tick.major").data(h,String),x=M.enter().insert("g",".domain").attr("class","tick major").style("opacity",1e-6),b=ya.transition(M.exit()).style("opacity",1e-6).remove(),_=ya.transition(M).style("opacity",1),w=Bi(e),S=f.selectAll(".domain").data([0]),E=(S.enter().append("path").attr("class","domain"),ya.transition(S)),k=e.copy(),A=this.__chart__||k;
+this.__chart__=k,x.append("line"),x.append("text");var N=x.select("line"),q=_.select("line"),T=M.select("text").text(g),C=x.select("text"),z=_.select("text");switch(r){case"bottom":n=qu,d.attr("y2",u),y.attr("x2",0).attr("y2",u),N.attr("y2",i),C.attr("y",Math.max(i,0)+o),q.attr("x2",0).attr("y2",i),z.attr("x",0).attr("y",Math.max(i,0)+o),T.attr("dy",".71em").style("text-anchor","middle"),E.attr("d","M"+w[0]+","+a+"V0H"+w[1]+"V"+a);break;case"top":n=qu,d.attr("y2",-u),y.attr("x2",0).attr("y2",-u),N.attr("y2",-i),C.attr("y",-(Math.max(i,0)+o)),q.attr("x2",0).attr("y2",-i),z.attr("x",0).attr("y",-(Math.max(i,0)+o)),T.attr("dy","0em").style("text-anchor","middle"),E.attr("d","M"+w[0]+","+-a+"V0H"+w[1]+"V"+-a);break;case"left":n=Tu,d.attr("x2",-u),y.attr("x2",-u).attr("y2",0),N.attr("x2",-i),C.attr("x",-(Math.max(i,0)+o)),q.attr("x2",-i).attr("y2",0),z.attr("x",-(Math.max(i,0)+o)).attr("y",0),T.attr("dy",".32em").style("text-anchor","end"),E.attr("d","M"+-a+","+w[0]+"H0V"+w[1]+"H"+-a);break;case"right":n=Tu,d.attr("x2",u),y.attr("x2",u).attr("y2",0),N.attr("x2",i),C.attr("x",Math.max(i,0)+o),q.attr("x2",i).attr("y2",0),z.attr("x",Math.max(i,0)+o).attr("y",0),T.attr("dy",".32em").style("text-anchor","start"),E.attr("d","M"+a+","+w[0]+"H0V"+w[1]+"H"+a)}if(e.rangeBand){var D=k.rangeBand()/2,j=function(n){return k(n)+D};x.call(n,j),_.call(n,j)}else x.call(n,A),_.call(n,k),b.call(n,k),d.call(n,A),y.call(n,k),v.call(n,k)})}var t,e=ya.scale.linear(),r=Uc,i=6,u=6,a=6,o=3,c=[10],l=null,s=0;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Ic?t+"":Uc,n):r},n.ticks=function(){return arguments.length?(c=arguments,n):c},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t,e){if(!arguments.length)return i;var r=arguments.length-1;return i=+t,u=r>1?+e:i,a=r>0?+arguments[r]:i,n},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(t){return arguments.length?(s=+t,n):s},n};var Uc="bottom",Ic={top:1,right:1,bottom:1,left:1};ya.svg.brush=function(){function n(u){u.each(function(){var u,a=ya.select(this),s=a.selectAll(".background").data([0]),f=a.selectAll(".extent").data([0]),h=a.selectAll(".resize").data(l,String);a.style("pointer-events","all").on("mousedown.brush",i).on("touchstart.brush",i),s.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),f.enter().append("rect").attr("class","extent").style("cursor","move"),h.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Vc[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),h.style("display",n.empty()?"none":null),h.exit().remove(),o&&(u=Bi(o),s.attr("x",u[0]).attr("width",u[1]-u[0]),e(a)),c&&(u=Bi(c),s.attr("y",u[0]).attr("height",u[1]-u[0]),r(a)),t(a)})}function t(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)][0]+","+s[+/^s/.test(n)][1]+")"})}function e(n){n.select(".extent").attr("x",s[0][0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1][0]-s[0][0])}function r(n){n.select(".extent").attr("y",s[0][1]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",s[1][1]-s[0][1])}function i(){function i(){var n=ya.event.changedTouches;return n?ya.touches(M,n)[0]:ya.mouse(M)}function l(){32==ya.event.keyCode&&(k||(v=null,N[0]-=s[1][0],N[1]-=s[1][1],k=2),g())}function h(){32==ya.event.keyCode&&2==k&&(N[0]+=s[1][0],N[1]+=s[1][1],k=0,g())}function p(){var n=i(),u=!1;y&&(n[0]+=y[0],n[1]+=y[1]),k||(ya.event.altKey?(v||(v=[(s[0][0]+s[1][0])/2,(s[0][1]+s[1][1])/2]),N[0]=s[+(n[0]<v[0])][0],N[1]=s[+(n[1]<v[1])][1]):v=null),S&&m(n,o,0)&&(e(_),u=!0),E&&m(n,c,1)&&(r(_),u=!0),u&&(t(_),b({type:"brush",mode:k?"move":"resize"}))}function m(n,t,e){var r,i,a=Bi(t),o=a[0],c=a[1],l=N[e],h=s[1][e]-s[0][e];return k&&(o-=l,c-=h+l),r=f[e]?Math.max(o,Math.min(c,n[e])):n[e],k?i=(r+=l)+h:(v&&(l=Math.max(o,Math.min(c,2*v[e]-r))),r>l?(i=r,r=l):i=l),s[0][e]!==r||s[1][e]!==i?(u=null,s[0][e]=r,s[1][e]=i,!0):void 0}function d(){p(),_.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ya.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),A(),b({type:"brushend"})}var v,y,M=this,x=ya.select(ya.event.target),b=a.of(M,arguments),_=ya.select(M),w=x.datum(),S=!/^(n|s)$/.test(w)&&o,E=!/^(e|w)$/.test(w)&&c,k=x.classed("extent"),A=H(),N=i(),q=ya.select(ba).on("keydown.brush",l).on("keyup.brush",h);if(ya.event.changedTouches?q.on("touchmove.brush",p).on("touchend.brush",d):q.on("mousemove.brush",p).on("mouseup.brush",d),k)N[0]=s[0][0]-N[0],N[1]=s[0][1]-N[1];else if(w){var T=+/w$/.test(w),C=+/^n/.test(w);y=[s[1-T][0]-N[0],s[1-C][1]-N[1]],N[0]=s[T][0],N[1]=s[C][1]}else ya.event.altKey&&(v=N.slice());_.style("pointer-events","none").selectAll(".resize").style("display",null),ya.select("body").style("cursor",x.style("cursor")),b({type:"brushstart"}),p()}var u,a=m(n,"brushstart","brush","brushend"),o=null,c=null,l=Xc[0],s=[[0,0],[0,0]],f=[!0,!0];return n.x=function(t){return arguments.length?(o=t,l=Xc[!o<<1|!c],n):o},n.y=function(t){return arguments.length?(c=t,l=Xc[!o<<1|!c],n):c},n.clamp=function(t){return arguments.length?(o&&c?f=[!!t[0],!!t[1]]:(o||c)&&(f[+!o]=!!t),n):o&&c?f:o||c?f[+!o]:null},n.extent=function(t){var e,r,i,a,l;return arguments.length?(u=[[0,0],[0,0]],o&&(e=t[0],r=t[1],c&&(e=e[0],r=r[0]),u[0][0]=e,u[1][0]=r,o.invert&&(e=o(e),r=o(r)),e>r&&(l=e,e=r,r=l),s[0][0]=0|e,s[1][0]=0|r),c&&(i=t[0],a=t[1],o&&(i=i[1],a=a[1]),u[0][1]=i,u[1][1]=a,c.invert&&(i=c(i),a=c(a)),i>a&&(l=i,i=a,a=l),s[0][1]=0|i,s[1][1]=0|a),n):(t=u||s,o&&(e=t[0][0],r=t[1][0],u||(e=s[0][0],r=s[1][0],o.invert&&(e=o.invert(e),r=o.invert(r)),e>r&&(l=e,e=r,r=l))),c&&(i=t[0][1],a=t[1][1],u||(i=s[0][1],a=s[1][1],c.invert&&(i=c.invert(i),a=c.invert(a)),i>a&&(l=i,i=a,a=l))),o&&c?[[e,i],[r,a]]:o?[e,r]:c&&[i,a])},n.clear=function(){return u=null,s[0][0]=s[0][1]=s[1][0]=s[1][1]=0,n},n.empty=function(){return o&&s[0][0]===s[1][0]||c&&s[0][1]===s[1][1]},ya.rebind(n,a,"on")};var Vc={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Xc=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]];ya.time={};var Zc=Date,Bc=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];zu.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){$c.setUTCDate.apply(this._,arguments)},setDay:function(){$c.setUTCDay.apply(this._,arguments)},setFullYear:function(){$c.setUTCFullYear.apply(this._,arguments)},setHours:function(){$c.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){$c.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){$c.setUTCMinutes.apply(this._,arguments)},setMonth:function(){$c.setUTCMonth.apply(this._,arguments)},setSeconds:function(){$c.setUTCSeconds.apply(this._,arguments)},setTime:function(){$c.setTime.apply(this._,arguments)}};var $c=Date.prototype,Wc="%a %b %e %X %Y",Jc="%m/%d/%Y",Gc="%H:%M:%S",Kc=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],Qc=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],nl=["January","February","March","April","May","June","July","August","September","October","November","December"],tl=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];ya.time.year=Du(function(n){return n=ya.time.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ya.time.years=ya.time.year.range,ya.time.years.utc=ya.time.year.utc.range,ya.time.day=Du(function(n){var t=new Zc(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ya.time.days=ya.time.day.range,ya.time.days.utc=ya.time.day.utc.range,ya.time.dayOfYear=function(n){var t=ya.time.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},Bc.forEach(function(n,t){n=n.toLowerCase(),t=7-t;var e=ya.time[n]=Du(function(n){return(n=ya.time.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ya.time.year(n).getDay();return Math.floor((ya.time.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ya.time[n+"s"]=e.range,ya.time[n+"s"].utc=e.utc.range,ya.time[n+"OfYear"]=function(n){var e=ya.time.year(n).getDay();return Math.floor((ya.time.dayOfYear(n)+(e+t)%7)/7)}}),ya.time.week=ya.time.sunday,ya.time.weeks=ya.time.sunday.range,ya.time.weeks.utc=ya.time.sunday.utc.range,ya.time.weekOfYear=ya.time.sundayOfYear,ya.time.format=function(n){function t(t){for(var r,i,u,a=[],o=-1,c=0;++o<e;)37===n.charCodeAt(o)&&(a.push(n.substring(c,o)),null!=(i=fl[r=n.charAt(++o)])&&(r=n.charAt(++o)),(u=hl[r])&&(r=u(t,null==i?"e"===r?" ":"0":i)),a.push(r),c=o+1);return a.push(n.substring(c,o)),a.join("")}var e=n.length;return t.parse=function(t){var e={y:1900,m:0,d:1,H:0,M:0,S:0,L:0},r=Lu(e,n,t,0);if(r!=t.length)return null;"p"in e&&(e.H=e.H%12+12*e.p);var i=new Zc;return"j"in e?i.setFullYear(e.y,0,e.j):"w"in e&&("W"in e||"U"in e)?(i.setFullYear(e.y,0,1),i.setFullYear(e.y,0,"W"in e?(e.w+6)%7+7*e.W-(i.getDay()+5)%7:e.w+7*e.U-(i.getDay()+6)%7)):i.setFullYear(e.y,e.m,e.d),i.setHours(e.H,e.M,e.S,e.L),i},t.toString=function(){return n},t};var el=Hu(Kc),rl=Fu(Kc),il=Hu(Qc),ul=Fu(Qc),al=Hu(nl),ol=Fu(nl),cl=Hu(tl),ll=Fu(tl),sl=/^%/,fl={"-":"",_:" ",0:"0"},hl={a:function(n){return Qc[n.getDay()]},A:function(n){return Kc[n.getDay()]},b:function(n){return tl[n.getMonth()]},B:function(n){return nl[n.getMonth()]},c:ya.time.format(Wc),d:function(n,t){return Pu(n.getDate(),t,2)},e:function(n,t){return Pu(n.getDate(),t,2)},H:function(n,t){return Pu(n.getHours(),t,2)},I:function(n,t){return Pu(n.getHours()%12||12,t,2)},j:function(n,t){return Pu(1+ya.time.dayOfYear(n),t,3)},L:function(n,t){return Pu(n.getMilliseconds(),t,3)},m:function(n,t){return Pu(n.getMonth()+1,t,2)},M:function(n,t){return Pu(n.getMinutes(),t,2)},p:function(n){return n.getHours()>=12?"PM":"AM"},S:function(n,t){return Pu(n.getSeconds(),t,2)},U:function(n,t){return Pu(ya.time.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Pu(ya.time.mondayOfYear(n),t,2)},x:ya.time.format(Jc),X:ya.time.format(Gc),y:function(n,t){return Pu(n.getFullYear()%100,t,2)},Y:function(n,t){return Pu(n.getFullYear()%1e4,t,4)},Z:aa,"%":function(){return"%"}},gl={a:Ou,A:Yu,b:Vu,B:Xu,c:Zu,d:Qu,e:Qu,H:ta,I:ta,j:na,L:ia,m:Ku,M:ea,p:ua,S:ra,U:Uu,w:Ru,W:Iu,x:Bu,X:$u,y:Ju,Y:Wu,"%":oa},pl=/^\s*\d+/,ml=ya.map({am:0,pm:1});ya.time.format.utc=function(n){function t(n){try{Zc=zu;var t=new Zc;return t._=n,e(t)}finally{Zc=Date}}var e=ya.time.format(n);return t.parse=function(n){try{Zc=zu;var t=e.parse(n);return t&&t._}finally{Zc=Date}},t.toString=e.toString,t};var dl=ya.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");ya.time.format.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?ca:dl,ca.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},ca.toString=dl.toString,ya.time.second=Du(function(n){return new Zc(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ya.time.seconds=ya.time.second.range,ya.time.seconds.utc=ya.time.second.utc.range,ya.time.minute=Du(function(n){return new Zc(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ya.time.minutes=ya.time.minute.range,ya.time.minutes.utc=ya.time.minute.utc.range,ya.time.hour=Du(function(n){var t=n.getTimezoneOffset()/60;return new Zc(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ya.time.hours=ya.time.hour.range,ya.time.hours.utc=ya.time.hour.utc.range,ya.time.month=Du(function(n){return n=ya.time.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ya.time.months=ya.time.month.range,ya.time.months.utc=ya.time.month.utc.range;var vl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],yl=[[ya.time.second,1],[ya.time.second,5],[ya.time.second,15],[ya.time.second,30],[ya.time.minute,1],[ya.time.minute,5],[ya.time.minute,15],[ya.time.minute,30],[ya.time.hour,1],[ya.time.hour,3],[ya.time.hour,6],[ya.time.hour,12],[ya.time.day,1],[ya.time.day,2],[ya.time.week,1],[ya.time.month,1],[ya.time.month,3],[ya.time.year,1]],Ml=[[ya.time.format("%Y"),Xt],[ya.time.format("%B"),function(n){return n.getMonth()}],[ya.time.format("%b %d"),function(n){return 1!=n.getDate()}],[ya.time.format("%a %d"),function(n){return n.getDay()&&1!=n.getDate()}],[ya.time.format("%I %p"),function(n){return n.getHours()}],[ya.time.format("%I:%M"),function(n){return n.getMinutes()}],[ya.time.format(":%S"),function(n){return n.getSeconds()}],[ya.time.format(".%L"),function(n){return n.getMilliseconds()}]],xl=ya.scale.linear(),bl=fa(Ml);yl.year=function(n,t){return xl.domain(n.map(ga)).ticks(t).map(ha)},ya.time.scale=function(){return la(ya.scale.linear(),yl,bl)};var _l=yl.map(function(n){return[n[0].utc,n[1]]}),wl=[[ya.time.format.utc("%Y"),Xt],[ya.time.format.utc("%B"),function(n){return n.getUTCMonth()}],[ya.time.format.utc("%b %d"),function(n){return 1!=n.getUTCDate()}],[ya.time.format.utc("%a %d"),function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],[ya.time.format.utc("%I %p"),function(n){return n.getUTCHours()}],[ya.time.format.utc("%I:%M"),function(n){return n.getUTCMinutes()}],[ya.time.format.utc(":%S"),function(n){return n.getUTCSeconds()}],[ya.time.format.utc(".%L"),function(n){return n.getUTCMilliseconds()}]],Sl=fa(wl);return _l.year=function(n,t){return xl.domain(n.map(ma)).ticks(t).map(pa)},ya.time.scale.utc=function(){return la(ya.scale.linear(),_l,Sl)},ya.text=dt(function(n){return n.responseText}),ya.json=function(n,t){return vt(n,"application/json",da,t)},ya.html=function(n,t){return vt(n,"text/html",va,t)},ya.xml=dt(function(n){return n.responseXML}),ya}();
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/jquery.jqplot.css
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/jquery.jqplot.css	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/jquery.jqplot.css	(revision 2119)
@@ -0,0 +1,259 @@
+/*rules for the plot target div.  These will be cascaded down to all plot elements according to css rules*/
+.jqplot-target {
+    position: relative;
+    color: #666666;
+    font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
+    font-size: 1em;
+/*    height: 300px;
+    width: 400px;*/
+}
+
+/*rules applied to all axes*/
+.jqplot-axis {
+    font-size: 0.75em;
+}
+
+.jqplot-xaxis {
+    margin-top: 10px;
+}
+
+.jqplot-x2axis {
+    margin-bottom: 10px;
+}
+
+.jqplot-yaxis {
+    margin-right: 10px;
+}
+
+.jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, .jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis {
+    margin-left: 10px;
+    margin-right: 10px;
+}
+
+/*rules applied to all axis tick divs*/
+.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick, .jqplot-yMidAxis-tick {
+    position: absolute;
+    white-space: pre;
+}
+
+
+.jqplot-xaxis-tick {
+    top: 0px;
+    /* initial position untill tick is drawn in proper place */
+    left: 15px;
+/*    padding-top: 10px;*/
+    vertical-align: top;
+}
+
+.jqplot-x2axis-tick {
+    bottom: 0px;
+    /* initial position untill tick is drawn in proper place */
+    left: 15px;
+/*    padding-bottom: 10px;*/
+    vertical-align: bottom;
+}
+
+.jqplot-yaxis-tick {
+    right: 0px;
+    /* initial position untill tick is drawn in proper place */
+    top: 15px;
+/*    padding-right: 10px;*/
+    text-align: right;
+}
+
+.jqplot-yaxis-tick.jqplot-breakTick {
+    right: -20px;
+    margin-right: 0px;
+    padding:1px 5px 1px 5px;
+    /*background-color: white;*/
+    z-index: 2;
+    font-size: 1.5em;
+}
+
+.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick {
+    left: 0px;
+    /* initial position untill tick is drawn in proper place */
+    top: 15px;
+/*    padding-left: 10px;*/
+/*    padding-right: 15px;*/
+    text-align: left;
+}
+
+.jqplot-yMidAxis-tick {
+    text-align: center;
+    white-space: nowrap;
+}
+
+.jqplot-xaxis-label {
+    margin-top: 10px;
+    font-size: 11pt;
+    position: absolute;
+}
+
+.jqplot-x2axis-label {
+    margin-bottom: 10px;
+    font-size: 11pt;
+    position: absolute;
+}
+
+.jqplot-yaxis-label {
+    margin-right: 10px;
+/*    text-align: center;*/
+    font-size: 11pt;
+    position: absolute;
+}
+
+.jqplot-yMidAxis-label {
+    font-size: 11pt;
+    position: absolute;
+}
+
+.jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, .jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, .jqplot-y8axis-label, .jqplot-y9axis-label {
+/*    text-align: center;*/
+    font-size: 11pt;
+    margin-left: 10px;
+    position: absolute;
+}
+
+.jqplot-meterGauge-tick {
+    font-size: 0.75em;
+    color: #999999;
+}
+
+.jqplot-meterGauge-label {
+    font-size: 1em;
+    color: #999999;
+}
+
+table.jqplot-table-legend {
+    margin-top: 12px;
+    margin-bottom: 12px;
+    margin-left: 12px;
+    margin-right: 12px;
+}
+
+table.jqplot-table-legend, table.jqplot-cursor-legend {
+    background-color: rgba(255,255,255,0.6);
+    border: 1px solid #cccccc;
+    position: absolute;
+    font-size: 0.75em;
+}
+
+td.jqplot-table-legend {
+    vertical-align:middle;
+}
+
+/*
+These rules could be used instead of assigning
+element styles and relying on js object properties.
+*/
+
+/*
+td.jqplot-table-legend-swatch {
+    padding-top: 0.5em;
+    text-align: center;
+}
+
+tr.jqplot-table-legend:first td.jqplot-table-legend-swatch {
+    padding-top: 0px;
+}
+*/
+
+td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active {
+    cursor: pointer;
+}
+
+.jqplot-table-legend .jqplot-series-hidden {
+    text-decoration: line-through;
+}
+
+div.jqplot-table-legend-swatch-outline {
+    border: 1px solid #cccccc;
+    padding:1px;
+}
+
+div.jqplot-table-legend-swatch {
+    width:0px;
+    height:0px;
+    border-top-width: 5px;
+    border-bottom-width: 5px;
+    border-left-width: 6px;
+    border-right-width: 6px;
+    border-top-style: solid;
+    border-bottom-style: solid;
+    border-left-style: solid;
+    border-right-style: solid;
+}
+
+.jqplot-title {
+    top: 0px;
+    left: 0px;
+    padding-bottom: 0.5em;
+    font-size: 1.2em;
+}
+
+table.jqplot-cursor-tooltip {
+    border: 1px solid #cccccc;
+    font-size: 0.75em;
+}
+
+
+.jqplot-cursor-tooltip {
+    border: 1px solid #cccccc;
+    font-size: 0.75em;
+    white-space: nowrap;
+    background: rgba(208,208,208,0.5);
+    padding: 1px;
+}
+
+.jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip {
+    border: 1px solid #cccccc;
+    font-size: 0.75em;
+    white-space: nowrap;
+    background: rgba(208,208,208,0.5);
+    padding: 1px;
+}
+
+.jqplot-point-label {
+    font-size: 0.75em;
+    z-index: 2;
+}
+      
+td.jqplot-cursor-legend-swatch {
+    vertical-align: middle;
+    text-align: center;
+}
+
+div.jqplot-cursor-legend-swatch {
+    width: 1.2em;
+    height: 0.7em;
+}
+
+.jqplot-error {
+/*   Styles added to the plot target container when there is an error go here.*/
+    text-align: center;
+}
+
+.jqplot-error-message {
+/*    Styling of the custom error message div goes here.*/
+    position: relative;
+    top: 46%;
+    display: inline-block;
+}
+
+div.jqplot-bubble-label {
+    font-size: 0.8em;
+/*    background: rgba(90%, 90%, 90%, 0.15);*/
+    padding-left: 2px;
+    padding-right: 2px;
+    color: rgb(20%, 20%, 20%);
+}
+
+div.jqplot-bubble-label.jqplot-bubble-label-highlight {
+    background: rgba(90%, 90%, 90%, 0.7);
+}
+
+div.jqplot-noData-container {
+    text-align: center;
+    background-color: rgba(96%, 96%, 96%, 0.3);
+}
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/jquery.jqplot.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/jquery.jqplot.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/jquery.jqplot.js	(revision 2119)
@@ -0,0 +1,11417 @@
+/**
+ * Title: jqPlot Charts
+ * 
+ * Pure JavaScript plotting plugin for jQuery.
+ * 
+ * About: Version
+ * 
+ * version: 1.0.8 
+ * revision: 1250
+ * 
+ * About: Copyright & License
+ * 
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT and GPL version 2.0 licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly.
+ * 
+ * See <GPL Version 2> and <MIT License> contained within this distribution for further information. 
+ *
+ * The author would appreciate an email letting him know of any substantial
+ * use of jqPlot.  You can reach the author at: chris at jqplot dot com 
+ * or see http://www.jqplot.com/info.php.  This is, of course, not required.
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php.
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ * 
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ * 
+ * About: Introduction
+ * 
+ * jqPlot requires jQuery (1.4+ required for certain features). jQuery 1.4.2 is included in the distribution.  
+ * To use jqPlot include jQuery, the jqPlot jQuery plugin, the jqPlot css file and optionally 
+ * the excanvas script for IE support in your web page:
+ * 
+ * > <!--[if lt IE 9]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]-->
+ * > <script language="javascript" type="text/javascript" src="jquery-1.4.4.min.js"></script>
+ * > <script language="javascript" type="text/javascript" src="jquery.jqplot.min.js"></script>
+ * > <link rel="stylesheet" type="text/css" href="jquery.jqplot.css" />
+ * 
+ * jqPlot can be customized by overriding the defaults of any of the objects which make
+ * up the plot. The general usage of jqplot is:
+ * 
+ * > chart = $.jqplot('targetElemId', [dataArray,...], {optionsObject});
+ * 
+ * The options available to jqplot are detailed in <jqPlot Options> in the jqPlotOptions.txt file.
+ * 
+ * An actual call to $.jqplot() may look like the 
+ * examples below:
+ * 
+ * > chart = $.jqplot('chartdiv',  [[[1, 2],[3,5.12],[5,13.1],[7,33.6],[9,85.9],[11,219.9]]]);
+ * 
+ * or
+ * 
+ * > dataArray = [34,12,43,55,77];
+ * > chart = $.jqplot('targetElemId', [dataArray, ...], {title:'My Plot', axes:{yaxis:{min:20, max:100}}});
+ * 
+ * For more inforrmation, see <jqPlot Usage>.
+ * 
+ * About: Usage
+ * 
+ * See <jqPlot Usage>
+ * 
+ * About: Available Options 
+ * 
+ * See <jqPlot Options> for a list of options available thorugh the options object (not complete yet!)
+ * 
+ * About: Options Usage
+ * 
+ * See <Options Tutorial>
+ * 
+ * About: Changes
+ * 
+ * See <Change Log>
+ * 
+ */
+
+(function($) {
+    // make sure undefined is undefined
+    var undefined;
+    
+    $.fn.emptyForce = function() {
+      for ( var i = 0, elem; (elem = $(this)[i]) != null; i++ ) {
+        // Remove element nodes and prevent memory leaks
+        if ( elem.nodeType === 1 ) {
+          $.cleanData( elem.getElementsByTagName("*") );
+        }
+  
+        // Remove any remaining nodes
+        if ($.jqplot.use_excanvas) {
+          elem.outerHTML = "";
+        }
+        else {
+          while ( elem.firstChild ) {
+            elem.removeChild( elem.firstChild );
+          }
+        }
+
+        elem = null;
+      }
+  
+      return $(this);
+    };
+  
+    $.fn.removeChildForce = function(parent) {
+      while ( parent.firstChild ) {
+        this.removeChildForce( parent.firstChild );
+        parent.removeChild( parent.firstChild );
+      }
+    };
+
+    $.fn.jqplot = function() {
+        var datas = [];
+        var options = [];
+        // see how many data arrays we have
+        for (var i=0, l=arguments.length; i<l; i++) {
+            if ($.isArray(arguments[i])) {
+                datas.push(arguments[i]);
+            }
+            else if ($.isPlainObject(arguments[i])) {
+                options.push(arguments[i]);
+            }
+        }
+
+        return this.each(function(index) {
+            var tid, 
+                plot, 
+                $this = $(this),
+                dl = datas.length,
+                ol = options.length,
+                data, 
+                opts;
+
+            if (index < dl) {
+                data = datas[index];
+            }
+            else {
+                data = dl ? datas[dl-1] : null;
+            }
+
+            if (index < ol) {
+                opts = options[index];
+            }
+            else {
+                opts = ol ? options[ol-1] : null;
+            }
+
+            // does el have an id?
+            // if not assign it one.
+            tid = $this.attr('id');
+            if (tid === undefined) {
+                tid = 'jqplot_target_' + $.jqplot.targetCounter++;
+                $this.attr('id', tid);
+            }
+
+            plot = $.jqplot(tid, data, opts);
+
+            $this.data('jqplot', plot);
+        });
+    };
+
+
+    /**
+     * Namespace: $.jqplot
+     * jQuery function called by the user to create a plot.
+     *  
+     * Parameters:
+     * target - ID of target element to render the plot into.
+     * data - an array of data series.
+     * options - user defined options object.  See the individual classes for available options.
+     * 
+     * Properties:
+     * config - object to hold configuration information for jqPlot plot object.
+     * 
+     * attributes:
+     * enablePlugins - False to disable plugins by default.  Plugins must then be explicitly 
+     *   enabled in the individual plot options.  Default: false.
+     *   This property sets the "show" property of certain plugins to true or false.
+     *   Only plugins that can be immediately active upon loading are affected.  This includes
+     *   non-renderer plugins like cursor, dragable, highlighter, and trendline.
+     * defaultHeight - Default height for plots where no css height specification exists.  This
+     *   is a jqplot wide default.
+     * defaultWidth - Default height for plots where no css height specification exists.  This
+     *   is a jqplot wide default.
+     */
+
+    $.jqplot = function(target, data, options) {
+        var _data = null, _options = null;
+
+        if (arguments.length === 3) {
+            _data = data;
+            _options = options;
+        }
+
+        else if (arguments.length === 2) {
+            if ($.isArray(data)) {
+                _data = data;
+            }
+
+            else if ($.isPlainObject(data)) {
+                _options = data;
+            }
+        }
+
+        if (_data === null && _options !== null && _options.data) {
+            _data = _options.data;
+        }
+
+        var plot = new jqPlot();
+        // remove any error class that may be stuck on target.
+        $('#'+target).removeClass('jqplot-error');
+        
+        if ($.jqplot.config.catchErrors) {
+            try {
+                plot.init(target, _data, _options);
+                plot.draw();
+                plot.themeEngine.init.call(plot);
+                return plot;
+            }
+            catch(e) {
+                var msg = $.jqplot.config.errorMessage || e.message;
+                $('#'+target).append('<div class="jqplot-error-message">'+msg+'</div>');
+                $('#'+target).addClass('jqplot-error');
+                document.getElementById(target).style.background = $.jqplot.config.errorBackground;
+                document.getElementById(target).style.border = $.jqplot.config.errorBorder;
+                document.getElementById(target).style.fontFamily = $.jqplot.config.errorFontFamily;
+                document.getElementById(target).style.fontSize = $.jqplot.config.errorFontSize;
+                document.getElementById(target).style.fontStyle = $.jqplot.config.errorFontStyle;
+                document.getElementById(target).style.fontWeight = $.jqplot.config.errorFontWeight;
+            }
+        }
+        else {        
+            plot.init(target, _data, _options);
+            plot.draw();
+            plot.themeEngine.init.call(plot);
+            return plot;
+        }
+    };
+
+    $.jqplot.version = "1.0.8";
+    $.jqplot.revision = "1250";
+
+    $.jqplot.targetCounter = 1;
+
+    // canvas manager to reuse canvases on the plot.
+    // Should help solve problem of canvases not being freed and
+    // problem of waiting forever for firefox to decide to free memory.
+    $.jqplot.CanvasManager = function() {
+        // canvases are managed globally so that they can be reused
+        // across plots after they have been freed
+        if (typeof $.jqplot.CanvasManager.canvases == 'undefined') {
+            $.jqplot.CanvasManager.canvases = [];
+            $.jqplot.CanvasManager.free = [];
+        }
+        
+        var myCanvases = [];
+        
+        this.getCanvas = function() {
+            var canvas;
+            var makeNew = true;
+            
+            if (!$.jqplot.use_excanvas) {
+                for (var i = 0, l = $.jqplot.CanvasManager.canvases.length; i < l; i++) {
+                    if ($.jqplot.CanvasManager.free[i] === true) {
+                        makeNew = false;
+                        canvas = $.jqplot.CanvasManager.canvases[i];
+                        // $(canvas).removeClass('jqplot-canvasManager-free').addClass('jqplot-canvasManager-inuse');
+                        $.jqplot.CanvasManager.free[i] = false;
+                        myCanvases.push(i);
+                        break;
+                    }
+                }
+            }
+
+            if (makeNew) {
+                canvas = document.createElement('canvas');
+                myCanvases.push($.jqplot.CanvasManager.canvases.length);
+                $.jqplot.CanvasManager.canvases.push(canvas);
+                $.jqplot.CanvasManager.free.push(false);
+            }   
+            
+            return canvas;
+        };
+        
+        // this method has to be used after settings the dimesions
+        // on the element returned by getCanvas()
+        this.initCanvas = function(canvas) {
+            if ($.jqplot.use_excanvas) {
+                return window.G_vmlCanvasManager.initElement(canvas);
+            }
+            return canvas;
+        };
+
+        this.freeAllCanvases = function() {
+            for (var i = 0, l=myCanvases.length; i < l; i++) {
+                this.freeCanvas(myCanvases[i]);
+            }
+            myCanvases = [];
+        };
+
+        this.freeCanvas = function(idx) {
+            if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+                // excanvas can't be reused, but properly unset
+                window.G_vmlCanvasManager.uninitElement($.jqplot.CanvasManager.canvases[idx]);
+                $.jqplot.CanvasManager.canvases[idx] = null;
+            } 
+            else {
+                var canvas = $.jqplot.CanvasManager.canvases[idx];
+                canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
+                $(canvas).unbind().removeAttr('class').removeAttr('style');
+                // Style attributes seemed to be still hanging around.  wierd.  Some ticks
+                // still retained a left: 0px attribute after reusing a canvas.
+                $(canvas).css({left: '', top: '', position: ''});
+                // setting size to 0 may save memory of unused canvases?
+                canvas.width = 0;
+                canvas.height = 0;
+                $.jqplot.CanvasManager.free[idx] = true;
+            }
+        };
+        
+    };
+
+            
+    // Convienence function that won't hang IE or FF without FireBug.
+    $.jqplot.log = function() {
+        if (window.console) {
+            window.console.log.apply(window.console, arguments);
+        }
+    };
+        
+    $.jqplot.config = {
+        addDomReference: false,
+        enablePlugins:false,
+        defaultHeight:300,
+        defaultWidth:400,
+        UTCAdjust:false,
+        timezoneOffset: new Date(new Date().getTimezoneOffset() * 60000),
+        errorMessage: '',
+        errorBackground: '',
+        errorBorder: '',
+        errorFontFamily: '',
+        errorFontSize: '',
+        errorFontStyle: '',
+        errorFontWeight: '',
+        catchErrors: false,
+        defaultTickFormatString: "%.1f",
+        defaultColors: [ "#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
+        defaultNegativeColors: [ "#498991", "#C08840", "#9F9274", "#546D61", "#646C4A", "#6F6621", "#6E3F5F", "#4F64B0", "#A89050", "#C45923", "#187399", "#945381", "#959E5C", "#C7AF7B", "#478396", "#907294"],
+        dashLength: 4,
+        gapLength: 4,
+        dotGapLength: 2.5,
+        srcLocation: 'jqplot/src/',
+        pluginLocation: 'jqplot/src/plugins/'
+    };
+    
+    
+    $.jqplot.arrayMax = function( array ){
+        return Math.max.apply( Math, array );
+    };
+    
+    $.jqplot.arrayMin = function( array ){
+        return Math.min.apply( Math, array );
+    };
+    
+    $.jqplot.enablePlugins = $.jqplot.config.enablePlugins;
+    
+    // canvas related tests taken from modernizer:
+    // Copyright (c) 2009 - 2010 Faruk Ates.
+    // http://www.modernizr.com
+    
+    $.jqplot.support_canvas = function() {
+        if (typeof $.jqplot.support_canvas.result == 'undefined') {
+            $.jqplot.support_canvas.result = !!document.createElement('canvas').getContext; 
+        }
+        return $.jqplot.support_canvas.result;
+    };
+            
+    $.jqplot.support_canvas_text = function() {
+        if (typeof $.jqplot.support_canvas_text.result == 'undefined') {
+            if (window.G_vmlCanvasManager !== undefined && window.G_vmlCanvasManager._version > 887) {
+                $.jqplot.support_canvas_text.result = true;
+            }
+            else {
+                $.jqplot.support_canvas_text.result = !!(document.createElement('canvas').getContext && typeof document.createElement('canvas').getContext('2d').fillText == 'function');
+            }
+             
+        }
+        return $.jqplot.support_canvas_text.result;
+    };
+    
+    $.jqplot.use_excanvas = ((!$.support.boxModel || !$.support.objectAll || !$support.leadingWhitespace) && !$.jqplot.support_canvas()) ? true : false;
+    
+    /**
+     * 
+     * Hooks: jqPlot Pugin Hooks
+     * 
+     * $.jqplot.preInitHooks - called before initialization.
+     * $.jqplot.postInitHooks - called after initialization.
+     * $.jqplot.preParseOptionsHooks - called before user options are parsed.
+     * $.jqplot.postParseOptionsHooks - called after user options are parsed.
+     * $.jqplot.preDrawHooks - called before plot draw.
+     * $.jqplot.postDrawHooks - called after plot draw.
+     * $.jqplot.preDrawSeriesHooks - called before each series is drawn.
+     * $.jqplot.postDrawSeriesHooks - called after each series is drawn.
+     * $.jqplot.preDrawLegendHooks - called before the legend is drawn.
+     * $.jqplot.addLegendRowHooks - called at the end of legend draw, so plugins
+     *     can add rows to the legend table.
+     * $.jqplot.preSeriesInitHooks - called before series is initialized.
+     * $.jqplot.postSeriesInitHooks - called after series is initialized.
+     * $.jqplot.preParseSeriesOptionsHooks - called before series related options
+     *     are parsed.
+     * $.jqplot.postParseSeriesOptionsHooks - called after series related options
+     *     are parsed.
+     * $.jqplot.eventListenerHooks - called at the end of plot drawing, binds
+     *     listeners to the event canvas which lays on top of the grid area.
+     * $.jqplot.preDrawSeriesShadowHooks - called before series shadows are drawn.
+     * $.jqplot.postDrawSeriesShadowHooks - called after series shadows are drawn.
+     * 
+     */
+    
+    $.jqplot.preInitHooks = [];
+    $.jqplot.postInitHooks = [];
+    $.jqplot.preParseOptionsHooks = [];
+    $.jqplot.postParseOptionsHooks = [];
+    $.jqplot.preDrawHooks = [];
+    $.jqplot.postDrawHooks = [];
+    $.jqplot.preDrawSeriesHooks = [];
+    $.jqplot.postDrawSeriesHooks = [];
+    $.jqplot.preDrawLegendHooks = [];
+    $.jqplot.addLegendRowHooks = [];
+    $.jqplot.preSeriesInitHooks = [];
+    $.jqplot.postSeriesInitHooks = [];
+    $.jqplot.preParseSeriesOptionsHooks = [];
+    $.jqplot.postParseSeriesOptionsHooks = [];
+    $.jqplot.eventListenerHooks = [];
+    $.jqplot.preDrawSeriesShadowHooks = [];
+    $.jqplot.postDrawSeriesShadowHooks = [];
+
+    // A superclass holding some common properties and methods.
+    $.jqplot.ElemContainer = function() {
+        this._elem;
+        this._plotWidth;
+        this._plotHeight;
+        this._plotDimensions = {height:null, width:null};
+    };
+    
+    $.jqplot.ElemContainer.prototype.createElement = function(el, offsets, clss, cssopts, attrib) {
+        this._offsets = offsets;
+        var klass = clss || 'jqplot';
+        var elem = document.createElement(el);
+        this._elem = $(elem);
+        this._elem.addClass(klass);
+        this._elem.css(cssopts);
+        this._elem.attr(attrib);
+        // avoid memory leak;
+        elem = null;
+        return this._elem;
+    };
+    
+    $.jqplot.ElemContainer.prototype.getWidth = function() {
+        if (this._elem) {
+            return this._elem.outerWidth(true);
+        }
+        else {
+            return null;
+        }
+    };
+    
+    $.jqplot.ElemContainer.prototype.getHeight = function() {
+        if (this._elem) {
+            return this._elem.outerHeight(true);
+        }
+        else {
+            return null;
+        }
+    };
+    
+    $.jqplot.ElemContainer.prototype.getPosition = function() {
+        if (this._elem) {
+            return this._elem.position();
+        }
+        else {
+            return {top:null, left:null, bottom:null, right:null};
+        }
+    };
+    
+    $.jqplot.ElemContainer.prototype.getTop = function() {
+        return this.getPosition().top;
+    };
+    
+    $.jqplot.ElemContainer.prototype.getLeft = function() {
+        return this.getPosition().left;
+    };
+    
+    $.jqplot.ElemContainer.prototype.getBottom = function() {
+        return this._elem.css('bottom');
+    };
+    
+    $.jqplot.ElemContainer.prototype.getRight = function() {
+        return this._elem.css('right');
+    };
+    
+
+    /**
+     * Class: Axis
+     * An individual axis object.  Cannot be instantiated directly, but created
+     * by the Plot object.  Axis properties can be set or overridden by the 
+     * options passed in from the user.
+     * 
+     */
+    function Axis(name) {
+        $.jqplot.ElemContainer.call(this);
+        // Group: Properties
+        //
+        // Axes options are specified within an axes object at the top level of the 
+        // plot options like so:
+        // > {
+        // >    axes: {
+        // >        xaxis: {min: 5},
+        // >        yaxis: {min: 2, max: 8, numberTicks:4},
+        // >        x2axis: {pad: 1.5},
+        // >        y2axis: {ticks:[22, 44, 66, 88]}
+        // >        }
+        // > }
+        // There are 2 x axes, 'xaxis' and 'x2axis', and 
+        // 9 yaxes, 'yaxis', 'y2axis'. 'y3axis', ...  Any or all of which may be specified.
+        this.name = name;
+        this._series = [];
+        // prop: show
+        // Wether to display the axis on the graph.
+        this.show = false;
+        // prop: tickRenderer
+        // A class of a rendering engine for creating the ticks labels displayed on the plot, 
+        // See <$.jqplot.AxisTickRenderer>.
+        this.tickRenderer = $.jqplot.AxisTickRenderer;
+        // prop: tickOptions
+        // Options that will be passed to the tickRenderer, see <$.jqplot.AxisTickRenderer> options.
+        this.tickOptions = {};
+        // prop: labelRenderer
+        // A class of a rendering engine for creating an axis label.
+        this.labelRenderer = $.jqplot.AxisLabelRenderer;
+        // prop: labelOptions
+        // Options passed to the label renderer.
+        this.labelOptions = {};
+        // prop: label
+        // Label for the axis
+        this.label = null;
+        // prop: showLabel
+        // true to show the axis label.
+        this.showLabel = true;
+        // prop: min
+        // minimum value of the axis (in data units, not pixels).
+        this.min = null;
+        // prop: max
+        // maximum value of the axis (in data units, not pixels).
+        this.max = null;
+        // prop: autoscale
+        // DEPRECATED
+        // the default scaling algorithm produces superior results.
+        this.autoscale = false;
+        // prop: pad
+        // Padding to extend the range above and below the data bounds.
+        // The data range is multiplied by this factor to determine minimum and maximum axis bounds.
+        // A value of 0 will be interpreted to mean no padding, and pad will be set to 1.0.
+        this.pad = 1.2;
+        // prop: padMax
+        // Padding to extend the range above data bounds.
+        // The top of the data range is multiplied by this factor to determine maximum axis bounds.
+        // A value of 0 will be interpreted to mean no padding, and padMax will be set to 1.0.
+        this.padMax = null;
+        // prop: padMin
+        // Padding to extend the range below data bounds.
+        // The bottom of the data range is multiplied by this factor to determine minimum axis bounds.
+        // A value of 0 will be interpreted to mean no padding, and padMin will be set to 1.0.
+        this.padMin = null;
+        // prop: ticks
+        // 1D [val, val, ...] or 2D [[val, label], [val, label], ...] array of ticks for the axis.
+        // If no label is specified, the value is formatted into an appropriate label.
+        this.ticks = [];
+        // prop: numberTicks
+        // Desired number of ticks.  Default is to compute automatically.
+        this.numberTicks;
+        // prop: tickInterval
+        // number of units between ticks.  Mutually exclusive with numberTicks.
+        this.tickInterval;
+        // prop: renderer
+        // A class of a rendering engine that handles tick generation, 
+        // scaling input data to pixel grid units and drawing the axis element.
+        this.renderer = $.jqplot.LinearAxisRenderer;
+        // prop: rendererOptions
+        // renderer specific options.  See <$.jqplot.LinearAxisRenderer> for options.
+        this.rendererOptions = {};
+        // prop: showTicks
+        // Wether to show the ticks (both marks and labels) or not.
+        // Will not override showMark and showLabel options if specified on the ticks themselves.
+        this.showTicks = true;
+        // prop: showTickMarks
+        // Wether to show the tick marks (line crossing grid) or not.
+        // Overridden by showTicks and showMark option of tick itself.
+        this.showTickMarks = true;
+        // prop: showMinorTicks
+        // Wether or not to show minor ticks.  This is renderer dependent.
+        this.showMinorTicks = true;
+        // prop: drawMajorGridlines
+        // True to draw gridlines for major axis ticks.
+        this.drawMajorGridlines = true;
+        // prop: drawMinorGridlines
+        // True to draw gridlines for minor ticks.
+        this.drawMinorGridlines = false;
+        // prop: drawMajorTickMarks
+        // True to draw tick marks for major axis ticks.
+        this.drawMajorTickMarks = true;
+        // prop: drawMinorTickMarks
+        // True to draw tick marks for minor ticks.  This is renderer dependent.
+        this.drawMinorTickMarks = true;
+        // prop: useSeriesColor
+        // Use the color of the first series associated with this axis for the
+        // tick marks and line bordering this axis.
+        this.useSeriesColor = false;
+        // prop: borderWidth
+        // width of line stroked at the border of the axis.  Defaults
+        // to the width of the grid boarder.
+        this.borderWidth = null;
+        // prop: borderColor
+        // color of the border adjacent to the axis.  Defaults to grid border color.
+        this.borderColor = null;
+        // prop: scaleToHiddenSeries
+        // True to include hidden series when computing axes bounds and scaling.
+        this.scaleToHiddenSeries = false;
+        // minimum and maximum values on the axis.
+        this._dataBounds = {min:null, max:null};
+        // statistics (min, max, mean) as well as actual data intervals for each series attached to axis.
+        // holds collection of {intervals:[], min:, max:, mean: } objects for each series on axis.
+        this._intervalStats = [];
+        // pixel position from the top left of the min value and max value on the axis.
+        this._offsets = {min:null, max:null};
+        this._ticks=[];
+        this._label = null;
+        // prop: syncTicks
+        // true to try and synchronize tick spacing across multiple axes so that ticks and
+        // grid lines line up.  This has an impact on autoscaling algorithm, however.
+        // In general, autoscaling an individual axis will work better if it does not
+        // have to sync ticks.
+        this.syncTicks = null;
+        // prop: tickSpacing
+        // Approximate pixel spacing between ticks on graph.  Used during autoscaling.
+        // This number will be an upper bound, actual spacing will be less.
+        this.tickSpacing = 75;
+        // Properties to hold the original values for min, max, ticks, tickInterval and numberTicks
+        // so they can be restored if altered by plugins.
+        this._min = null;
+        this._max = null;
+        this._tickInterval = null;
+        this._numberTicks = null;
+        this.__ticks = null;
+        // hold original user options.
+        this._options = {};
+    }
+    
+    Axis.prototype = new $.jqplot.ElemContainer();
+    Axis.prototype.constructor = Axis;
+    
+    Axis.prototype.init = function() {
+        if ($.isFunction(this.renderer)) {
+            this.renderer = new this.renderer();  
+        }
+        // set the axis name
+        this.tickOptions.axis = this.name;
+        // if showMark or showLabel tick options not specified, use value of axis option.
+        // showTicks overrides showTickMarks.
+        if (this.tickOptions.showMark == null) {
+            this.tickOptions.showMark = this.showTicks;
+        }
+        if (this.tickOptions.showMark == null) {
+            this.tickOptions.showMark = this.showTickMarks;
+        }
+        if (this.tickOptions.showLabel == null) {
+            this.tickOptions.showLabel = this.showTicks;
+        }
+        
+        if (this.label == null || this.label == '') {
+            this.showLabel = false;
+        }
+        else {
+            this.labelOptions.label = this.label;
+        }
+        if (this.showLabel == false) {
+            this.labelOptions.show = false;
+        }
+        // set the default padMax, padMin if not specified
+        // special check, if no padding desired, padding
+        // should be set to 1.0
+        if (this.pad == 0) {
+            this.pad = 1.0;
+        }
+        if (this.padMax == 0) {
+            this.padMax = 1.0;
+        }
+        if (this.padMin == 0) {
+            this.padMin = 1.0;
+        }
+        if (this.padMax == null) {
+            this.padMax = (this.pad-1)/2 + 1;
+        }
+        if (this.padMin == null) {
+            this.padMin = (this.pad-1)/2 + 1;
+        }
+        // now that padMin and padMax are correctly set, reset pad in case user has supplied 
+        // padMin and/or padMax
+        this.pad = this.padMax + this.padMin - 1;
+        if (this.min != null || this.max != null) {
+            this.autoscale = false;
+        }
+        // if not set, sync ticks for y axes but not x by default.
+        if (this.syncTicks == null && this.name.indexOf('y') > -1) {
+            this.syncTicks = true;
+        }
+        else if (this.syncTicks == null){
+            this.syncTicks = false;
+        }
+        this.renderer.init.call(this, this.rendererOptions);
+        
+    };
+    
+    Axis.prototype.draw = function(ctx, plot) {
+        // Memory Leaks patch
+        if (this.__ticks) {
+          this.__ticks = null;
+        }
+
+        return this.renderer.draw.call(this, ctx, plot);
+        
+    };
+    
+    Axis.prototype.set = function() {
+        this.renderer.set.call(this);
+    };
+    
+    Axis.prototype.pack = function(pos, offsets) {
+        if (this.show) {
+            this.renderer.pack.call(this, pos, offsets);
+        }
+        // these properties should all be available now.
+        if (this._min == null) {
+            this._min = this.min;
+            this._max = this.max;
+            this._tickInterval = this.tickInterval;
+            this._numberTicks = this.numberTicks;
+            this.__ticks = this._ticks;
+        }
+    };
+    
+    // reset the axis back to original values if it has been scaled, zoomed, etc.
+    Axis.prototype.reset = function() {
+        this.renderer.reset.call(this);
+    };
+    
+    Axis.prototype.resetScale = function(opts) {
+        $.extend(true, this, {min: null, max: null, numberTicks: null, tickInterval: null, _ticks: [], ticks: []}, opts);
+        this.resetDataBounds();
+    };
+    
+    Axis.prototype.resetDataBounds = function() {
+        // Go through all the series attached to this axis and find
+        // the min/max bounds for this axis.
+        var db = this._dataBounds;
+        db.min = null;
+        db.max = null;
+        var l, s, d;
+        // check for when to force min 0 on bar series plots.
+        var doforce = (this.show) ? true : false;
+        for (var i=0; i<this._series.length; i++) {
+            s = this._series[i];
+            if (s.show || this.scaleToHiddenSeries) {
+                d = s._plotData;
+                if (s._type === 'line' && s.renderer.bands.show && this.name.charAt(0) !== 'x') {
+                    d = [[0, s.renderer.bands._min], [1, s.renderer.bands._max]];
+                }
+
+                var minyidx = 1, maxyidx = 1;
+
+                if (s._type != null && s._type == 'ohlc') {
+                    minyidx = 3;
+                    maxyidx = 2;
+                }
+                
+                for (var j=0, l=d.length; j<l; j++) { 
+                    if (this.name == 'xaxis' || this.name == 'x2axis') {
+                        if ((d[j][0] != null && d[j][0] < db.min) || db.min == null) {
+                            db.min = d[j][0];
+                        }
+                        if ((d[j][0] != null && d[j][0] > db.max) || db.max == null) {
+                            db.max = d[j][0];
+                        }
+                    }              
+                    else {
+                        if ((d[j][minyidx] != null && d[j][minyidx] < db.min) || db.min == null) {
+                            db.min = d[j][minyidx];
+                        }
+                        if ((d[j][maxyidx] != null && d[j][maxyidx] > db.max) || db.max == null) {
+                            db.max = d[j][maxyidx];
+                        }
+                    }              
+                }
+
+                // Hack to not pad out bottom of bar plots unless user has specified a padding.
+                // every series will have a chance to set doforce to false.  once it is set to 
+                // false, it cannot be reset to true.
+                // If any series attached to axis is not a bar, wont force 0.
+                if (doforce && s.renderer.constructor !== $.jqplot.BarRenderer) {
+                    doforce = false;
+                }
+
+                else if (doforce && this._options.hasOwnProperty('forceTickAt0') && this._options.forceTickAt0 == false) {
+                    doforce = false;
+                }
+
+                else if (doforce && s.renderer.constructor === $.jqplot.BarRenderer) {
+                    if (s.barDirection == 'vertical' && this.name != 'xaxis' && this.name != 'x2axis') { 
+                        if (this._options.pad != null || this._options.padMin != null) {
+                            doforce = false;
+                        }
+                    }
+
+                    else if (s.barDirection == 'horizontal' && (this.name == 'xaxis' || this.name == 'x2axis')) {
+                        if (this._options.pad != null || this._options.padMin != null) {
+                            doforce = false;
+                        }
+                    }
+
+                }
+            }
+        }
+
+        if (doforce && this.renderer.constructor === $.jqplot.LinearAxisRenderer && db.min >= 0) {
+            this.padMin = 1.0;
+            this.forceTickAt0 = true;
+        }
+    };
+
+    /**
+     * Class: Legend
+     * Legend object.  Cannot be instantiated directly, but created
+     * by the Plot object.  Legend properties can be set or overridden by the 
+     * options passed in from the user.
+     */
+    function Legend(options) {
+        $.jqplot.ElemContainer.call(this);
+        // Group: Properties
+        
+        // prop: show
+        // Wether to display the legend on the graph.
+        this.show = false;
+        // prop: location
+        // Placement of the legend.  one of the compass directions: nw, n, ne, e, se, s, sw, w
+        this.location = 'ne';
+        // prop: labels
+        // Array of labels to use.  By default the renderer will look for labels on the series.
+        // Labels specified in this array will override labels specified on the series.
+        this.labels = [];
+        // prop: showLabels
+        // true to show the label text on the  legend.
+        this.showLabels = true;
+        // prop: showSwatch
+        // true to show the color swatches on the legend.
+        this.showSwatches = true;
+        // prop: placement
+        // "insideGrid" places legend inside the grid area of the plot.
+        // "outsideGrid" places the legend outside the grid but inside the plot container, 
+        // shrinking the grid to accomodate the legend.
+        // "inside" synonym for "insideGrid", 
+        // "outside" places the legend ouside the grid area, but does not shrink the grid which
+        // can cause the legend to overflow the plot container.
+        this.placement = "insideGrid";
+        // prop: xoffset
+        // DEPRECATED.  Set the margins on the legend using the marginTop, marginLeft, etc. 
+        // properties or via CSS margin styling of the .jqplot-table-legend class.
+        this.xoffset = 0;
+        // prop: yoffset
+        // DEPRECATED.  Set the margins on the legend using the marginTop, marginLeft, etc. 
+        // properties or via CSS margin styling of the .jqplot-table-legend class.
+        this.yoffset = 0;
+        // prop: border
+        // css spec for the border around the legend box.
+        this.border;
+        // prop: background
+        // css spec for the background of the legend box.
+        this.background;
+        // prop: textColor
+        // css color spec for the legend text.
+        this.textColor;
+        // prop: fontFamily
+        // css font-family spec for the legend text.
+        this.fontFamily; 
+        // prop: fontSize
+        // css font-size spec for the legend text.
+        this.fontSize ;
+        // prop: rowSpacing
+        // css padding-top spec for the rows in the legend.
+        this.rowSpacing = '0.5em';
+        // renderer
+        // A class that will create a DOM object for the legend,
+        // see <$.jqplot.TableLegendRenderer>.
+        this.renderer = $.jqplot.TableLegendRenderer;
+        // prop: rendererOptions
+        // renderer specific options passed to the renderer.
+        this.rendererOptions = {};
+        // prop: predraw
+        // Wether to draw the legend before the series or not.
+        // Used with series specific legend renderers for pie, donut, mekko charts, etc.
+        this.preDraw = false;
+        // prop: marginTop
+        // CSS margin for the legend DOM element. This will set an element 
+        // CSS style for the margin which will override any style sheet setting.
+        // The default will be taken from the stylesheet.
+        this.marginTop = null;
+        // prop: marginRight
+        // CSS margin for the legend DOM element. This will set an element 
+        // CSS style for the margin which will override any style sheet setting.
+        // The default will be taken from the stylesheet.
+        this.marginRight = null;
+        // prop: marginBottom
+        // CSS margin for the legend DOM element. This will set an element 
+        // CSS style for the margin which will override any style sheet setting.
+        // The default will be taken from the stylesheet.
+        this.marginBottom = null;
+        // prop: marginLeft
+        // CSS margin for the legend DOM element. This will set an element 
+        // CSS style for the margin which will override any style sheet setting.
+        // The default will be taken from the stylesheet.
+        this.marginLeft = null;
+        // prop: escapeHtml
+        // True to escape special characters with their html entity equivalents
+        // in legend text.  "<" becomes &lt; and so on, so html tags are not rendered.
+        this.escapeHtml = false;
+        this._series = [];
+        
+        $.extend(true, this, options);
+    }
+    
+    Legend.prototype = new $.jqplot.ElemContainer();
+    Legend.prototype.constructor = Legend;
+    
+    Legend.prototype.setOptions = function(options) {
+        $.extend(true, this, options);
+        
+        // Try to emulate deprecated behaviour
+        // if user has specified xoffset or yoffset, copy these to
+        // the margin properties.
+        
+        if (this.placement ==  'inside') {
+            this.placement = 'insideGrid';
+        }
+        
+        if (this.xoffset >0) {
+            if (this.placement == 'insideGrid') {
+                switch (this.location) {
+                    case 'nw':
+                    case 'w':
+                    case 'sw':
+                        if (this.marginLeft == null) {
+                            this.marginLeft = this.xoffset + 'px';
+                        }
+                        this.marginRight = '0px';
+                        break;
+                    case 'ne':
+                    case 'e':
+                    case 'se':
+                    default:
+                        if (this.marginRight == null) {
+                            this.marginRight = this.xoffset + 'px';
+                        }
+                        this.marginLeft = '0px';
+                        break;
+                }
+            }
+            else if (this.placement == 'outside') {
+                switch (this.location) {
+                    case 'nw':
+                    case 'w':
+                    case 'sw':
+                        if (this.marginRight == null) {
+                            this.marginRight = this.xoffset + 'px';
+                        }
+                        this.marginLeft = '0px';
+                        break;
+                    case 'ne':
+                    case 'e':
+                    case 'se':
+                    default:
+                        if (this.marginLeft == null) {
+                            this.marginLeft = this.xoffset + 'px';
+                        }
+                        this.marginRight = '0px';
+                        break;
+                }
+            }
+            this.xoffset = 0;
+        }
+        
+        if (this.yoffset >0) {
+            if (this.placement == 'outside') {
+                switch (this.location) {
+                    case 'sw':
+                    case 's':
+                    case 'se':
+                        if (this.marginTop == null) {
+                            this.marginTop = this.yoffset + 'px';
+                        }
+                        this.marginBottom = '0px';
+                        break;
+                    case 'ne':
+                    case 'n':
+                    case 'nw':
+                    default:
+                        if (this.marginBottom == null) {
+                            this.marginBottom = this.yoffset + 'px';
+                        }
+                        this.marginTop = '0px';
+                        break;
+                }
+            }
+            else if (this.placement == 'insideGrid') {
+                switch (this.location) {
+                    case 'sw':
+                    case 's':
+                    case 'se':
+                        if (this.marginBottom == null) {
+                            this.marginBottom = this.yoffset + 'px';
+                        }
+                        this.marginTop = '0px';
+                        break;
+                    case 'ne':
+                    case 'n':
+                    case 'nw':
+                    default:
+                        if (this.marginTop == null) {
+                            this.marginTop = this.yoffset + 'px';
+                        }
+                        this.marginBottom = '0px';
+                        break;
+                }
+            }
+            this.yoffset = 0;
+        }
+        
+        // TO-DO:
+        // Handle case where offsets are < 0.
+        //
+    };
+    
+    Legend.prototype.init = function() {
+        if ($.isFunction(this.renderer)) {
+            this.renderer = new this.renderer();  
+        }
+        this.renderer.init.call(this, this.rendererOptions);
+    };
+    
+    Legend.prototype.draw = function(offsets, plot) {
+        for (var i=0; i<$.jqplot.preDrawLegendHooks.length; i++){
+            $.jqplot.preDrawLegendHooks[i].call(this, offsets);
+        }
+        return this.renderer.draw.call(this, offsets, plot);
+    };
+    
+    Legend.prototype.pack = function(offsets) {
+        this.renderer.pack.call(this, offsets);
+    };
+
+    /**
+     * Class: Title
+     * Plot Title object.  Cannot be instantiated directly, but created
+     * by the Plot object.  Title properties can be set or overridden by the 
+     * options passed in from the user.
+     * 
+     * Parameters:
+     * text - text of the title.
+     */
+    function Title(text) {
+        $.jqplot.ElemContainer.call(this);
+        // Group: Properties
+        
+        // prop: text
+        // text of the title;
+        this.text = text;
+        // prop: show
+        // whether or not to show the title
+        this.show = true;
+        // prop: fontFamily
+        // css font-family spec for the text.
+        this.fontFamily;
+        // prop: fontSize
+        // css font-size spec for the text.
+        this.fontSize ;
+        // prop: textAlign
+        // css text-align spec for the text.
+        this.textAlign;
+        // prop: textColor
+        // css color spec for the text.
+        this.textColor;
+        // prop: renderer
+        // A class for creating a DOM element for the title,
+        // see <$.jqplot.DivTitleRenderer>.
+        this.renderer = $.jqplot.DivTitleRenderer;
+        // prop: rendererOptions
+        // renderer specific options passed to the renderer.
+        this.rendererOptions = {};   
+        // prop: escapeHtml
+        // True to escape special characters with their html entity equivalents
+        // in title text.  "<" becomes &lt; and so on, so html tags are not rendered.
+        this.escapeHtml = false;
+    }
+    
+    Title.prototype = new $.jqplot.ElemContainer();
+    Title.prototype.constructor = Title;
+    
+    Title.prototype.init = function() {
+        if ($.isFunction(this.renderer)) {
+            this.renderer = new this.renderer();  
+        }
+        this.renderer.init.call(this, this.rendererOptions);
+    };
+    
+    Title.prototype.draw = function(width) {
+        return this.renderer.draw.call(this, width);
+    };
+    
+    Title.prototype.pack = function() {
+        this.renderer.pack.call(this);
+    };
+
+
+    /**
+     * Class: Series
+     * An individual data series object.  Cannot be instantiated directly, but created
+     * by the Plot object.  Series properties can be set or overridden by the 
+     * options passed in from the user.
+     */
+    function Series(options) {
+        options = options || {};
+        $.jqplot.ElemContainer.call(this);
+        // Group: Properties
+        // Properties will be assigned from a series array at the top level of the
+        // options.  If you had two series and wanted to change the color and line
+        // width of the first and set the second to use the secondary y axis with
+        // no shadow and supply custom labels for each:
+        // > {
+        // >    series:[
+        // >        {color: '#ff4466', lineWidth: 5, label:'good line'},
+        // >        {yaxis: 'y2axis', shadow: false, label:'bad line'}
+        // >    ]
+        // > }
+
+        // prop: show
+        // whether or not to draw the series.
+        this.show = true;
+        // prop: xaxis
+        // which x axis to use with this series, either 'xaxis' or 'x2axis'.
+        this.xaxis = 'xaxis';
+        this._xaxis;
+        // prop: yaxis
+        // which y axis to use with this series, either 'yaxis' or 'y2axis'.
+        this.yaxis = 'yaxis';
+        this._yaxis;
+        this.gridBorderWidth = 2.0;
+        // prop: renderer
+        // A class of a renderer which will draw the series, 
+        // see <$.jqplot.LineRenderer>.
+        this.renderer = $.jqplot.LineRenderer;
+        // prop: rendererOptions
+        // Options to pass on to the renderer.
+        this.rendererOptions = {};
+        this.data = [];
+        this.gridData = [];
+        // prop: label
+        // Line label to use in the legend.
+        this.label = '';
+        // prop: showLabel
+        // true to show label for this series in the legend.
+        this.showLabel = true;
+        // prop: color
+        // css color spec for the series
+        this.color;
+        // prop: negativeColor
+        // css color spec used for filled (area) plots that are filled to zero and
+        // the "useNegativeColors" option is true.
+        this.negativeColor;
+        // prop: lineWidth
+        // width of the line in pixels.  May have different meanings depending on renderer.
+        this.lineWidth = 2.5;
+        // prop: lineJoin
+        // Canvas lineJoin style between segments of series.
+        this.lineJoin = 'round';
+        // prop: lineCap
+        // Canvas lineCap style at ends of line.
+        this.lineCap = 'round';
+        // prop: linePattern
+        // line pattern 'dashed', 'dotted', 'solid', some combination
+        // of '-' and '.' characters such as '.-.' or a numerical array like 
+        // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line, 
+        // [1, 10, 20, 10] to draw a dot-dash line, and so on.
+        this.linePattern = 'solid';
+        this.shadow = true;
+        // prop: shadowAngle
+        // Shadow angle in degrees
+        this.shadowAngle = 45;
+        // prop: shadowOffset
+        // Shadow offset from line in pixels
+        this.shadowOffset = 1.25;
+        // prop: shadowDepth
+        // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
+        this.shadowDepth = 3;
+        // prop: shadowAlpha
+        // Alpha channel transparency of shadow.  0 = transparent.
+        this.shadowAlpha = '0.1';
+        // prop: breakOnNull
+        // Wether line segments should be be broken at null value.
+        // False will join point on either side of line.
+        this.breakOnNull = false;
+        // prop: markerRenderer
+        // A class of a renderer which will draw marker (e.g. circle, square, ...) at the data points,
+        // see <$.jqplot.MarkerRenderer>.
+        this.markerRenderer = $.jqplot.MarkerRenderer;
+        // prop: markerOptions
+        // renderer specific options to pass to the markerRenderer,
+        // see <$.jqplot.MarkerRenderer>.
+        this.markerOptions = {};
+        // prop: showLine
+        // whether to actually draw the line or not.  Series will still be renderered, even if no line is drawn.
+        this.showLine = true;
+        // prop: showMarker
+        // whether or not to show the markers at the data points.
+        this.showMarker = true;
+        // prop: index
+        // 0 based index of this series in the plot series array.
+        this.index;
+        // prop: fill
+        // true or false, whether to fill under lines or in bars.
+        // May not be implemented in all renderers.
+        this.fill = false;
+        // prop: fillColor
+        // CSS color spec to use for fill under line.  Defaults to line color.
+        this.fillColor;
+        // prop: fillAlpha
+        // Alpha transparency to apply to the fill under the line.
+        // Use this to adjust alpha separate from fill color.
+        this.fillAlpha;
+        // prop: fillAndStroke
+        // If true will stroke the line (with color this.color) as well as fill under it.
+        // Applies only when fill is true.
+        this.fillAndStroke = false;
+        // prop: disableStack
+        // true to not stack this series with other series in the plot.
+        // To render properly, non-stacked series must come after any stacked series
+        // in the plot's data series array.  So, the plot's data series array would look like:
+        // > [stackedSeries1, stackedSeries2, ..., nonStackedSeries1, nonStackedSeries2, ...]
+        // disableStack will put a gap in the stacking order of series, and subsequent
+        // stacked series will not fill down through the non-stacked series and will
+        // most likely not stack properly on top of the non-stacked series.
+        this.disableStack = false;
+        // _stack is set by the Plot if the plot is a stacked chart.
+        // will stack lines or bars on top of one another to build a "mountain" style chart.
+        // May not be implemented in all renderers.
+        this._stack = false;
+        // prop: neighborThreshold
+        // how close or far (in pixels) the cursor must be from a point marker to detect the point.
+        this.neighborThreshold = 4;
+        // prop: fillToZero
+        // true will force bar and filled series to fill toward zero on the fill Axis.
+        this.fillToZero = false;
+        // prop: fillToValue
+        // fill a filled series to this value on the fill axis.
+        // Works in conjunction with fillToZero, so that must be true.
+        this.fillToValue = 0;
+        // prop: fillAxis
+        // Either 'x' or 'y'.  Which axis to fill the line toward if fillToZero is true.
+        // 'y' means fill up/down to 0 on the y axis for this series.
+        this.fillAxis = 'y';
+        // prop: useNegativeColors
+        // true to color negative values differently in filled and bar charts.
+        this.useNegativeColors = true;
+        this._stackData = [];
+        // _plotData accounts for stacking.  If plots not stacked, _plotData and data are same.  If
+        // stacked, _plotData is accumulation of stacking data.
+        this._plotData = [];
+        // _plotValues hold the individual x and y values that will be plotted for this series.
+        this._plotValues = {x:[], y:[]};
+        // statistics about the intervals between data points.  Used for auto scaling.
+        this._intervals = {x:{}, y:{}};
+        // data from the previous series, for stacked charts.
+        this._prevPlotData = [];
+        this._prevGridData = [];
+        this._stackAxis = 'y';
+        this._primaryAxis = '_xaxis';
+        // give each series a canvas to draw on.  This should allow for redrawing speedups.
+        this.canvas = new $.jqplot.GenericCanvas();
+        this.shadowCanvas = new $.jqplot.GenericCanvas();
+        this.plugins = {};
+        // sum of y values in this series.
+        this._sumy = 0;
+        this._sumx = 0;
+        this._type = '';
+    }
+    
+    Series.prototype = new $.jqplot.ElemContainer();
+    Series.prototype.constructor = Series;
+    
+    Series.prototype.init = function(index, gridbw, plot) {
+        // weed out any null values in the data.
+        this.index = index;
+        this.gridBorderWidth = gridbw;
+        var d = this.data;
+        var temp = [], i, l;
+        for (i=0, l=d.length; i<l; i++) {
+            if (! this.breakOnNull) {
+                if (d[i] == null || d[i][0] == null || d[i][1] == null) {
+                    continue;
+                }
+                else {
+                    temp.push(d[i]);
+                }
+            }
+            else {
+                // TODO: figure out what to do with null values
+                // probably involve keeping nulls in data array
+                // and then updating renderers to break line
+                // when it hits null value.
+                // For now, just keep value.
+                temp.push(d[i]);
+            }
+        }
+        this.data = temp;
+
+        // parse the renderer options and apply default colors if not provided
+        // Set color even if not shown, so series don't change colors when other
+        // series on plot shown/hidden.
+        if (!this.color) {
+            this.color = plot.colorGenerator.get(this.index);
+        }
+        if (!this.negativeColor) {
+            this.negativeColor = plot.negativeColorGenerator.get(this.index);
+        }
+
+
+        if (!this.fillColor) {
+            this.fillColor = this.color;
+        }
+        if (this.fillAlpha) {
+            var comp = $.jqplot.normalize2rgb(this.fillColor);
+            var comp = $.jqplot.getColorComponents(comp);
+            this.fillColor = 'rgba('+comp[0]+','+comp[1]+','+comp[2]+','+this.fillAlpha+')';
+        }
+        if ($.isFunction(this.renderer)) {
+            this.renderer = new this.renderer();  
+        }
+        this.renderer.init.call(this, this.rendererOptions, plot);
+        this.markerRenderer = new this.markerRenderer();
+        if (!this.markerOptions.color) {
+            this.markerOptions.color = this.color;
+        }
+        if (this.markerOptions.show == null) {
+            this.markerOptions.show = this.showMarker;
+        }
+        this.showMarker = this.markerOptions.show;
+        // the markerRenderer is called within its own scope, don't want to overwrite series options!!
+        this.markerRenderer.init(this.markerOptions);
+    };
+    
+    // data - optional data point array to draw using this series renderer
+    // gridData - optional grid data point array to draw using this series renderer
+    // stackData - array of cumulative data for stacked plots.
+    Series.prototype.draw = function(sctx, opts, plot) {
+        var options = (opts == undefined) ? {} : opts;
+        sctx = (sctx == undefined) ? this.canvas._ctx : sctx;
+        
+        var j, data, gridData;
+        
+        // hooks get called even if series not shown
+        // we don't clear canvas here, it would wipe out all other series as well.
+        for (j=0; j<$.jqplot.preDrawSeriesHooks.length; j++) {
+            $.jqplot.preDrawSeriesHooks[j].call(this, sctx, options);
+        }
+        if (this.show) {
+            this.renderer.setGridData.call(this, plot);
+            if (!options.preventJqPlotSeriesDrawTrigger) {
+                $(sctx.canvas).trigger('jqplotSeriesDraw', [this.data, this.gridData]);
+            }
+            data = [];
+            if (options.data) {
+                data = options.data;
+            }
+            else if (!this._stack) {
+                data = this.data;
+            }
+            else {
+                data = this._plotData;
+            }
+            gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot);
+
+            if (this._type === 'line' && this.renderer.smooth && this.renderer._smoothedData.length) {
+                gridData = this.renderer._smoothedData;
+            }
+
+            this.renderer.draw.call(this, sctx, gridData, options, plot);
+        }
+        
+        for (j=0; j<$.jqplot.postDrawSeriesHooks.length; j++) {
+            $.jqplot.postDrawSeriesHooks[j].call(this, sctx, options, plot);
+        }
+        
+        sctx = opts = plot = j = data = gridData = null;
+    };
+    
+    Series.prototype.drawShadow = function(sctx, opts, plot) {
+        var options = (opts == undefined) ? {} : opts;
+        sctx = (sctx == undefined) ? this.shadowCanvas._ctx : sctx;
+        
+        var j, data, gridData;
+        
+        // hooks get called even if series not shown
+        // we don't clear canvas here, it would wipe out all other series as well.
+        for (j=0; j<$.jqplot.preDrawSeriesShadowHooks.length; j++) {
+            $.jqplot.preDrawSeriesShadowHooks[j].call(this, sctx, options);
+        }
+        if (this.shadow) {
+            this.renderer.setGridData.call(this, plot);
+
+            data = [];
+            if (options.data) {
+                data = options.data;
+            }
+            else if (!this._stack) {
+                data = this.data;
+            }
+            else {
+                data = this._plotData;
+            }
+            gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot);
+        
+            this.renderer.drawShadow.call(this, sctx, gridData, options, plot);
+        }
+        
+        for (j=0; j<$.jqplot.postDrawSeriesShadowHooks.length; j++) {
+            $.jqplot.postDrawSeriesShadowHooks[j].call(this, sctx, options);
+        }
+        
+        sctx = opts = plot = j = data = gridData = null;
+        
+    };
+    
+    // toggles series display on plot, e.g. show/hide series
+    Series.prototype.toggleDisplay = function(ev, callback) {
+        var s, speed;
+        if (ev.data.series) {
+            s = ev.data.series;
+        }
+        else {
+            s = this;
+        }
+
+        if (ev.data.speed) {
+            speed = ev.data.speed;
+        }
+        if (speed) {
+            // this can be tricky because series may not have a canvas element if replotting.
+            if (s.canvas._elem.is(':hidden') || !s.show) {
+                s.show = true;
+
+                s.canvas._elem.removeClass('jqplot-series-hidden');
+                if (s.shadowCanvas._elem) {
+                    s.shadowCanvas._elem.fadeIn(speed);
+                }
+                s.canvas._elem.fadeIn(speed, callback);
+                s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeIn(speed);
+            }
+            else {
+                s.show = false;
+
+                s.canvas._elem.addClass('jqplot-series-hidden');
+                if (s.shadowCanvas._elem) {
+                    s.shadowCanvas._elem.fadeOut(speed);
+                }
+                s.canvas._elem.fadeOut(speed, callback);
+                s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeOut(speed);
+            }
+        }
+        else {
+            // this can be tricky because series may not have a canvas element if replotting.
+            if (s.canvas._elem.is(':hidden') || !s.show) {
+                s.show = true;
+
+                s.canvas._elem.removeClass('jqplot-series-hidden');
+                if (s.shadowCanvas._elem) {
+                    s.shadowCanvas._elem.show();
+                }
+                s.canvas._elem.show(0, callback);
+                s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).show();
+            }
+            else {
+                s.show = false;
+
+                s.canvas._elem.addClass('jqplot-series-hidden');
+                if (s.shadowCanvas._elem) {
+                    s.shadowCanvas._elem.hide();
+                }
+                s.canvas._elem.hide(0, callback);
+                s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).hide();
+            }
+        }
+    };
+    
+
+
+    /**
+     * Class: Grid
+     * 
+     * Object representing the grid on which the plot is drawn.  The grid in this
+     * context is the area bounded by the axes, the area which will contain the series.
+     * Note, the series are drawn on their own canvas.
+     * The Grid object cannot be instantiated directly, but is created by the Plot object.  
+     * Grid properties can be set or overridden by the options passed in from the user.
+     */
+    function Grid() {
+        $.jqplot.ElemContainer.call(this);
+        // Group: Properties
+        
+        // prop: drawGridlines
+        // whether to draw the gridlines on the plot.
+        this.drawGridlines = true;
+        // prop: gridLineColor
+        // color of the grid lines.
+        this.gridLineColor = '#cccccc';
+        // prop: gridLineWidth
+        // width of the grid lines.
+        this.gridLineWidth = 1.0;
+        // prop: background
+        // css spec for the background color.
+        this.background = '#fffdf6';
+        // prop: borderColor
+        // css spec for the color of the grid border.
+        this.borderColor = '#999999';
+        // prop: borderWidth
+        // width of the border in pixels.
+        this.borderWidth = 2.0;
+        // prop: drawBorder
+        // True to draw border around grid.
+        this.drawBorder = true;
+        // prop: shadow
+        // whether to show a shadow behind the grid.
+        this.shadow = true;
+        // prop: shadowAngle
+        // shadow angle in degrees
+        this.shadowAngle = 45;
+        // prop: shadowOffset
+        // Offset of each shadow stroke from the border in pixels
+        this.shadowOffset = 1.5;
+        // prop: shadowWidth
+        // width of the stoke for the shadow
+        this.shadowWidth = 3;
+        // prop: shadowDepth
+        // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
+        this.shadowDepth = 3;
+        // prop: shadowColor
+        // an optional css color spec for the shadow in 'rgba(n, n, n, n)' form
+        this.shadowColor = null;
+        // prop: shadowAlpha
+        // Alpha channel transparency of shadow.  0 = transparent.
+        this.shadowAlpha = '0.07';
+        this._left;
+        this._top;
+        this._right;
+        this._bottom;
+        this._width;
+        this._height;
+        this._axes = [];
+        // prop: renderer
+        // Instance of a renderer which will actually render the grid,
+        // see <$.jqplot.CanvasGridRenderer>.
+        this.renderer = $.jqplot.CanvasGridRenderer;
+        // prop: rendererOptions
+        // Options to pass on to the renderer,
+        // see <$.jqplot.CanvasGridRenderer>.
+        this.rendererOptions = {};
+        this._offsets = {top:null, bottom:null, left:null, right:null};
+    }
+    
+    Grid.prototype = new $.jqplot.ElemContainer();
+    Grid.prototype.constructor = Grid;
+    
+    Grid.prototype.init = function() {
+        if ($.isFunction(this.renderer)) {
+            this.renderer = new this.renderer();  
+        }
+        this.renderer.init.call(this, this.rendererOptions);
+    };
+    
+    Grid.prototype.createElement = function(offsets,plot) {
+        this._offsets = offsets;
+        return this.renderer.createElement.call(this, plot);
+    };
+    
+    Grid.prototype.draw = function() {
+        this.renderer.draw.call(this);
+    };
+    
+    $.jqplot.GenericCanvas = function() {
+        $.jqplot.ElemContainer.call(this);
+        this._ctx;  
+    };
+    
+    $.jqplot.GenericCanvas.prototype = new $.jqplot.ElemContainer();
+    $.jqplot.GenericCanvas.prototype.constructor = $.jqplot.GenericCanvas;
+    
+    $.jqplot.GenericCanvas.prototype.createElement = function(offsets, clss, plotDimensions, plot) {
+        this._offsets = offsets;
+        var klass = 'jqplot';
+        if (clss != undefined) {
+            klass = clss;
+        }
+        var elem;
+
+        elem = plot.canvasManager.getCanvas();
+        
+        // if new plotDimensions supplied, use them.
+        if (plotDimensions != null) {
+            this._plotDimensions = plotDimensions;
+        }
+        
+        elem.width = this._plotDimensions.width - this._offsets.left - this._offsets.right;
+        elem.height = this._plotDimensions.height - this._offsets.top - this._offsets.bottom;
+        this._elem = $(elem);
+        this._elem.css({ position: 'absolute', left: this._offsets.left, top: this._offsets.top });
+        
+        this._elem.addClass(klass);
+        
+        elem = plot.canvasManager.initCanvas(elem);
+        
+        elem = null;
+        return this._elem;
+    };
+    
+    $.jqplot.GenericCanvas.prototype.setContext = function() {
+        this._ctx = this._elem.get(0).getContext("2d");
+        return this._ctx;
+    };
+    
+    // Memory Leaks patch
+    $.jqplot.GenericCanvas.prototype.resetCanvas = function() {
+      if (this._elem) {
+        if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+           window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
+        }
+        
+        //this._elem.remove();
+        this._elem.emptyForce();
+      }
+      
+      this._ctx = null;
+    };
+    
+    $.jqplot.HooksManager = function () {
+        this.hooks =[];
+        this.args = [];
+    };
+    
+    $.jqplot.HooksManager.prototype.addOnce = function(fn, args) {
+        args = args || [];
+        var havehook = false;
+        for (var i=0, l=this.hooks.length; i<l; i++) {
+            if (this.hooks[i] == fn) {
+                havehook = true;
+            }
+        }
+        if (!havehook) {
+            this.hooks.push(fn);
+            this.args.push(args);
+        }
+    };
+    
+    $.jqplot.HooksManager.prototype.add = function(fn, args) {
+        args = args || [];
+        this.hooks.push(fn);
+        this.args.push(args);
+    };
+    
+    $.jqplot.EventListenerManager = function () {
+        this.hooks =[];
+    };
+    
+    $.jqplot.EventListenerManager.prototype.addOnce = function(ev, fn) {
+        var havehook = false, h, i;
+        for (var i=0, l=this.hooks.length; i<l; i++) {
+            h = this.hooks[i];
+            if (h[0] == ev && h[1] == fn) {
+                havehook = true;
+            }
+        }
+        if (!havehook) {
+            this.hooks.push([ev, fn]);
+        }
+    };
+    
+    $.jqplot.EventListenerManager.prototype.add = function(ev, fn) {
+        this.hooks.push([ev, fn]);
+    };
+
+
+    var _axisNames = ['yMidAxis', 'xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis'];
+
+    /**
+     * Class: jqPlot
+     * Plot object returned by call to $.jqplot.  Handles parsing user options,
+     * creating sub objects (Axes, legend, title, series) and rendering the plot.
+     */
+    function jqPlot() {
+        // Group: Properties
+        // These properties are specified at the top of the options object
+        // like so:
+        // > {
+        // >     axesDefaults:{min:0},
+        // >     series:[{color:'#6633dd'}],
+        // >     title: 'A Plot'
+        // > }
+        //
+
+        // prop: animate
+        // True to animate the series on initial plot draw (renderer dependent).
+        // Actual animation functionality must be supported in the renderer.
+        this.animate = false;
+        // prop: animateReplot
+        // True to animate series after a call to the replot() method.
+        // Use with caution!  Replots can happen very frequently under
+        // certain circumstances (e.g. resizing, dragging points) and
+        // animation in these situations can cause problems.
+        this.animateReplot = false;
+        // prop: axes
+        // up to 4 axes are supported, each with its own options, 
+        // See <Axis> for axis specific options.
+        this.axes = {xaxis: new Axis('xaxis'), yaxis: new Axis('yaxis'), x2axis: new Axis('x2axis'), y2axis: new Axis('y2axis'), y3axis: new Axis('y3axis'), y4axis: new Axis('y4axis'), y5axis: new Axis('y5axis'), y6axis: new Axis('y6axis'), y7axis: new Axis('y7axis'), y8axis: new Axis('y8axis'), y9axis: new Axis('y9axis'), yMidAxis: new Axis('yMidAxis')};
+        this.baseCanvas = new $.jqplot.GenericCanvas();
+        // true to intercept right click events and fire a 'jqplotRightClick' event.
+        // this will also block the context menu.
+        this.captureRightClick = false;
+        // prop: data
+        // user's data.  Data should *NOT* be specified in the options object,
+        // but be passed in as the second argument to the $.jqplot() function.
+        // The data property is described here soley for reference. 
+        // The data should be in the form of an array of 2D or 1D arrays like
+        // > [ [[x1, y1], [x2, y2],...], [y1, y2, ...] ].
+        this.data = [];
+        // prop: dataRenderer
+        // A callable which can be used to preprocess data passed into the plot.
+        // Will be called with 3 arguments: the plot data, a reference to the plot,
+        // and the value of dataRendererOptions.
+        this.dataRenderer;
+        // prop: dataRendererOptions
+        // Options that will be passed to the dataRenderer.
+        // Can be of any type.
+        this.dataRendererOptions;
+        this.defaults = {
+            // prop: axesDefaults
+            // default options that will be applied to all axes.
+            // see <Axis> for axes options.
+            axesDefaults: {},
+            axes: {xaxis:{}, yaxis:{}, x2axis:{}, y2axis:{}, y3axis:{}, y4axis:{}, y5axis:{}, y6axis:{}, y7axis:{}, y8axis:{}, y9axis:{}, yMidAxis:{}},
+            // prop: seriesDefaults
+            // default options that will be applied to all series.
+            // see <Series> for series options.
+            seriesDefaults: {},
+            series:[]
+        };
+        // prop: defaultAxisStart
+        // 1-D data series are internally converted into 2-D [x,y] data point arrays
+        // by jqPlot.  This is the default starting value for the missing x or y value.
+        // The added data will be a monotonically increasing series (e.g. [1, 2, 3, ...])
+        // starting at this value.
+        this.defaultAxisStart = 1;
+        // this.doCustomEventBinding = true;
+        // prop: drawIfHidden
+        // True to execute the draw method even if the plot target is hidden.
+        // Generally, this should be false.  Most plot elements will not be sized/
+        // positioned correclty if renderered into a hidden container.  To render into
+        // a hidden container, call the replot method when the container is shown.
+        this.drawIfHidden = false;
+        this.eventCanvas = new $.jqplot.GenericCanvas();
+        // prop: fillBetween
+        // Fill between 2 line series in a plot.
+        // Options object:
+        // {
+        //    series1: first index (0 based) of series in fill
+        //    series2: second index (0 based) of series in fill
+        //    color: color of fill [default fillColor of series1]
+        //    baseSeries:  fill will be drawn below this series (0 based index)
+        //    fill: false to turn off fill [default true].
+        //  }
+        this.fillBetween = {
+            series1: null,
+            series2: null,
+            color: null,
+            baseSeries: 0,
+            fill: true
+        };
+        // prop; fontFamily
+        // css spec for the font-family attribute.  Default for the entire plot.
+        this.fontFamily;
+        // prop: fontSize
+        // css spec for the font-size attribute.  Default for the entire plot.
+        this.fontSize;
+        // prop: grid
+        // See <Grid> for grid specific options.
+        this.grid = new Grid();
+        // prop: legend
+        // see <$.jqplot.TableLegendRenderer>
+        this.legend = new Legend();
+        // prop: noDataIndicator
+        // Options to set up a mock plot with a data loading indicator if no data is specified.
+        this.noDataIndicator = {    
+            show: false,
+            indicator: 'Loading Data...',
+            axes: {
+                xaxis: {
+                    min: 0,
+                    max: 10,
+                    tickInterval: 2,
+                    show: true
+                },
+                yaxis: {
+                    min: 0,
+                    max: 12,
+                    tickInterval: 3,
+                    show: true
+                }
+            }
+        };
+        // prop: negativeSeriesColors 
+        // colors to use for portions of the line below zero.
+        this.negativeSeriesColors = $.jqplot.config.defaultNegativeColors;
+        // container to hold all of the merged options.  Convienence for plugins.
+        this.options = {};
+        this.previousSeriesStack = [];
+        // Namespace to hold plugins.  Generally non-renderer plugins add themselves to here.
+        this.plugins = {};
+        // prop: series
+        // Array of series object options.
+        // see <Series> for series specific options.
+        this.series = [];
+        // array of series indices. Keep track of order
+        // which series canvases are displayed, lowest
+        // to highest, back to front.
+        this.seriesStack = [];
+        // prop: seriesColors
+        // Ann array of CSS color specifications that will be applied, in order,
+        // to the series in the plot.  Colors will wrap around so, if their
+        // are more series than colors, colors will be reused starting at the
+        // beginning.  For pie charts, this specifies the colors of the slices.
+        this.seriesColors = $.jqplot.config.defaultColors;
+        // prop: sortData
+        // false to not sort the data passed in by the user.
+        // Many bar, stacked and other graphs as well as many plugins depend on
+        // having sorted data.
+        this.sortData = true;
+        // prop: stackSeries
+        // true or false, creates a stack or "mountain" plot.
+        // Not all series renderers may implement this option.
+        this.stackSeries = false;
+        // a shortcut for axis syncTicks options.  Not implemented yet.
+        this.syncXTicks = true;
+        // a shortcut for axis syncTicks options.  Not implemented yet.
+        this.syncYTicks = true;
+        // the jquery object for the dom target.
+        this.target = null; 
+        // The id of the dom element to render the plot into
+        this.targetId = null;
+        // prop textColor
+        // css spec for the css color attribute.  Default for the entire plot.
+        this.textColor;
+        // prop: title
+        // Title object.  See <Title> for specific options.  As a shortcut, you
+        // can specify the title option as just a string like: title: 'My Plot'
+        // and this will create a new title object with the specified text.
+        this.title = new Title();
+        // Count how many times the draw method has been called while the plot is visible.
+        // Mostly used to test if plot has never been dran (=0), has been successfully drawn
+        // into a visible container once (=1) or draw more than once into a visible container.
+        // Can use this in tests to see if plot has been visibly drawn at least one time.
+        // After plot has been visibly drawn once, it generally doesn't need redrawing if its
+        // container is hidden and shown.
+        this._drawCount = 0;
+        // sum of y values for all series in plot.
+        // used in mekko chart.
+        this._sumy = 0;
+        this._sumx = 0;
+        // array to hold the cumulative stacked series data.
+        // used to ajust the individual series data, which won't have access to other
+        // series data.
+        this._stackData = [];
+        // array that holds the data to be plotted. This will be the series data
+        // merged with the the appropriate data from _stackData according to the stackAxis.
+        this._plotData = [];
+        this._width = null;
+        this._height = null; 
+        this._plotDimensions = {height:null, width:null};
+        this._gridPadding = {top:null, right:null, bottom:null, left:null};
+        this._defaultGridPadding = {top:10, right:10, bottom:23, left:10};
+
+        this._addDomReference = $.jqplot.config.addDomReference;
+
+        this.preInitHooks = new $.jqplot.HooksManager();
+        this.postInitHooks = new $.jqplot.HooksManager();
+        this.preParseOptionsHooks = new $.jqplot.HooksManager();
+        this.postParseOptionsHooks = new $.jqplot.HooksManager();
+        this.preDrawHooks = new $.jqplot.HooksManager();
+        this.postDrawHooks = new $.jqplot.HooksManager();
+        this.preDrawSeriesHooks = new $.jqplot.HooksManager();
+        this.postDrawSeriesHooks = new $.jqplot.HooksManager();
+        this.preDrawLegendHooks = new $.jqplot.HooksManager();
+        this.addLegendRowHooks = new $.jqplot.HooksManager();
+        this.preSeriesInitHooks = new $.jqplot.HooksManager();
+        this.postSeriesInitHooks = new $.jqplot.HooksManager();
+        this.preParseSeriesOptionsHooks = new $.jqplot.HooksManager();
+        this.postParseSeriesOptionsHooks = new $.jqplot.HooksManager();
+        this.eventListenerHooks = new $.jqplot.EventListenerManager();
+        this.preDrawSeriesShadowHooks = new $.jqplot.HooksManager();
+        this.postDrawSeriesShadowHooks = new $.jqplot.HooksManager();
+        
+        this.colorGenerator = new $.jqplot.ColorGenerator();
+        this.negativeColorGenerator = new $.jqplot.ColorGenerator();
+
+        this.canvasManager = new $.jqplot.CanvasManager();
+
+        this.themeEngine = new $.jqplot.ThemeEngine();
+        
+        var seriesColorsIndex = 0;
+
+        // Group: methods
+        //
+        // method: init
+        // sets the plot target, checks data and applies user
+        // options to plot.
+        this.init = function(target, data, options) {
+            options = options || {};
+            for (var i=0; i<$.jqplot.preInitHooks.length; i++) {
+                $.jqplot.preInitHooks[i].call(this, target, data, options);
+            }
+
+            for (var i=0; i<this.preInitHooks.hooks.length; i++) {
+                this.preInitHooks.hooks[i].call(this, target, data, options);
+            }
+            
+            this.targetId = '#'+target;
+            this.target = $('#'+target);
+
+            //////
+            // Add a reference to plot
+            //////
+            if (this._addDomReference) {
+                this.target.data('jqplot', this);
+            }
+            // remove any error class that may be stuck on target.
+            this.target.removeClass('jqplot-error');
+            if (!this.target.get(0)) {
+                throw new Error("No plot target specified");
+            }
+            
+            // make sure the target is positioned by some means and set css
+            if (this.target.css('position') == 'static') {
+                this.target.css('position', 'relative');
+            }
+            if (!this.target.hasClass('jqplot-target')) {
+                this.target.addClass('jqplot-target');
+            }
+            
+            // if no height or width specified, use a default.
+            if (!this.target.height()) {
+                var h;
+                if (options && options.height) {
+                    h = parseInt(options.height, 10);
+                }
+                else if (this.target.attr('data-height')) {
+                    h = parseInt(this.target.attr('data-height'), 10);
+                }
+                else {
+                    h = parseInt($.jqplot.config.defaultHeight, 10);
+                }
+                this._height = h;
+                this.target.css('height', h+'px');
+            }
+            else {
+                this._height = h = this.target.height();
+            }
+            if (!this.target.width()) {
+                var w;
+                if (options && options.width) {
+                    w = parseInt(options.width, 10);
+                }
+                else if (this.target.attr('data-width')) {
+                    w = parseInt(this.target.attr('data-width'), 10);
+                }
+                else {
+                    w = parseInt($.jqplot.config.defaultWidth, 10);
+                }
+                this._width = w;
+                this.target.css('width', w+'px');
+            }
+            else {
+                this._width = w = this.target.width();
+            }
+
+            for (var i=0, l=_axisNames.length; i<l; i++) {
+                this.axes[_axisNames[i]] = new Axis(_axisNames[i]);
+            }
+            
+            this._plotDimensions.height = this._height;
+            this._plotDimensions.width = this._width;
+            this.grid._plotDimensions = this._plotDimensions;
+            this.title._plotDimensions = this._plotDimensions;
+            this.baseCanvas._plotDimensions = this._plotDimensions;
+            this.eventCanvas._plotDimensions = this._plotDimensions;
+            this.legend._plotDimensions = this._plotDimensions;
+            if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
+                throw new Error("Canvas dimension not set");
+            }
+            
+            if (options.dataRenderer && $.isFunction(options.dataRenderer)) {
+                if (options.dataRendererOptions) {
+                    this.dataRendererOptions = options.dataRendererOptions;
+                }
+                this.dataRenderer = options.dataRenderer;
+                data = this.dataRenderer(data, this, this.dataRendererOptions);
+            }
+            
+            if (options.noDataIndicator && $.isPlainObject(options.noDataIndicator)) {
+                $.extend(true, this.noDataIndicator, options.noDataIndicator);
+            }
+            
+            if (data == null || $.isArray(data) == false || data.length == 0 || $.isArray(data[0]) == false || data[0].length == 0) {
+                
+                if (this.noDataIndicator.show == false) {
+                    throw new Error("No data specified");
+                }
+                
+                else {
+                    // have to be descructive here in order for plot to not try and render series.
+                    // This means that $.jqplot() will have to be called again when there is data.
+                    //delete options.series;
+                    
+                    for (var ax in this.noDataIndicator.axes) {
+                        for (var prop in this.noDataIndicator.axes[ax]) {
+                            this.axes[ax][prop] = this.noDataIndicator.axes[ax][prop];
+                        }
+                    }
+                    
+                    this.postDrawHooks.add(function() {
+                        var eh = this.eventCanvas.getHeight();
+                        var ew = this.eventCanvas.getWidth();
+                        var temp = $('<div class="jqplot-noData-container" style="position:absolute;"></div>');
+                        this.target.append(temp);
+                        temp.height(eh);
+                        temp.width(ew);
+                        temp.css('top', this.eventCanvas._offsets.top);
+                        temp.css('left', this.eventCanvas._offsets.left);
+                        
+                        var temp2 = $('<div class="jqplot-noData-contents" style="text-align:center; position:relative; margin-left:auto; margin-right:auto;"></div>');
+                        temp.append(temp2);
+                        temp2.html(this.noDataIndicator.indicator);
+                        var th = temp2.height();
+                        var tw = temp2.width();
+                        temp2.height(th);
+                        temp2.width(tw);
+                        temp2.css('top', (eh - th)/2 + 'px');
+                    });
+
+                }
+            }
+            
+            // make a copy of the data
+            this.data = $.extend(true, [], data);
+            
+            this.parseOptions(options);
+            
+            if (this.textColor) {
+                this.target.css('color', this.textColor);
+            }
+            if (this.fontFamily) {
+                this.target.css('font-family', this.fontFamily);
+            }
+            if (this.fontSize) {
+                this.target.css('font-size', this.fontSize);
+            }
+            
+            this.title.init();
+            this.legend.init();
+            this._sumy = 0;
+            this._sumx = 0;
+            this.computePlotData();
+            for (var i=0; i<this.series.length; i++) {
+                // set default stacking order for series canvases
+                this.seriesStack.push(i);
+                this.previousSeriesStack.push(i);
+                this.series[i].shadowCanvas._plotDimensions = this._plotDimensions;
+                this.series[i].canvas._plotDimensions = this._plotDimensions;
+                for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) {
+                    $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+                }
+                for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) {
+                    this.preSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+                }
+                // this.populatePlotData(this.series[i], i);
+                this.series[i]._plotDimensions = this._plotDimensions;
+                this.series[i].init(i, this.grid.borderWidth, this);
+                for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) {
+                    $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+                }
+                for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) {
+                    this.postSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+                }
+                this._sumy += this.series[i]._sumy;
+                this._sumx += this.series[i]._sumx;
+            }
+
+            var name,
+                axis;
+            for (var i=0, l=_axisNames.length; i<l; i++) {
+                name = _axisNames[i];
+                axis = this.axes[name];
+                axis._plotDimensions = this._plotDimensions;
+                axis.init();
+                if (this.axes[name].borderColor == null) {
+                    if (name.charAt(0) !== 'x' && axis.useSeriesColor === true && axis.show) {
+                        axis.borderColor = axis._series[0].color;
+                    }
+                    else {
+                        axis.borderColor = this.grid.borderColor;
+                    }
+                }
+            }
+            
+            if (this.sortData) {
+                sortData(this.series);
+            }
+            this.grid.init();
+            this.grid._axes = this.axes;
+            
+            this.legend._series = this.series;
+
+            for (var i=0; i<$.jqplot.postInitHooks.length; i++) {
+                $.jqplot.postInitHooks[i].call(this, target, this.data, options);
+            }
+
+            for (var i=0; i<this.postInitHooks.hooks.length; i++) {
+                this.postInitHooks.hooks[i].call(this, target, this.data, options);
+            }
+        };  
+        
+        // method: resetAxesScale
+        // Reset the specified axes min, max, numberTicks and tickInterval properties to null
+        // or reset these properties on all axes if no list of axes is provided.
+        //
+        // Parameters:
+        // axes - Boolean to reset or not reset all axes or an array or object of axis names to reset.
+        this.resetAxesScale = function(axes, options) {
+            var opts = options || {};
+            var ax = axes || this.axes;
+            if (ax === true) {
+                ax = this.axes;
+            }
+            if ($.isArray(ax)) {
+                for (var i = 0; i < ax.length; i++) {
+                    this.axes[ax[i]].resetScale(opts[ax[i]]);
+                }
+            }
+            else if (typeof(ax) === 'object') {
+                for (var name in ax) {
+                    this.axes[name].resetScale(opts[name]);
+                }
+            }
+        };
+        // method: reInitialize
+        // reinitialize plot for replotting.
+        // not called directly.
+        this.reInitialize = function (data, opts) {
+            // Plot should be visible and have a height and width.
+            // If plot doesn't have height and width for some
+            // reason, set it by other means.  Plot must not have
+            // a display:none attribute, however.
+
+            var options = $.extend(true, {}, this.options, opts);
+
+            var target = this.targetId.substr(1);
+            var tdata = (data == null) ? this.data : data;
+
+            for (var i=0; i<$.jqplot.preInitHooks.length; i++) {
+                $.jqplot.preInitHooks[i].call(this, target, tdata, options);
+            }
+
+            for (var i=0; i<this.preInitHooks.hooks.length; i++) {
+                this.preInitHooks.hooks[i].call(this, target, tdata, options);
+            }
+            
+            this._height = this.target.height();
+            this._width = this.target.width();
+            
+            if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
+                throw new Error("Target dimension not set");
+            }
+            
+            this._plotDimensions.height = this._height;
+            this._plotDimensions.width = this._width;
+            this.grid._plotDimensions = this._plotDimensions;
+            this.title._plotDimensions = this._plotDimensions;
+            this.baseCanvas._plotDimensions = this._plotDimensions;
+            this.eventCanvas._plotDimensions = this._plotDimensions;
+            this.legend._plotDimensions = this._plotDimensions;
+
+            var name,
+                t, 
+                j, 
+                axis;
+
+            for (var i=0, l=_axisNames.length; i<l; i++) {
+                name = _axisNames[i];
+                axis = this.axes[name];
+
+                // Memory Leaks patch : clear ticks elements
+                t = axis._ticks;
+                for (var j = 0, tlen = t.length; j < tlen; j++) {
+                  var el = t[j]._elem;
+                  if (el) {
+                    // if canvas renderer
+                    if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+                      window.G_vmlCanvasManager.uninitElement(el.get(0));
+                    }
+                    el.emptyForce();
+                    el = null;
+                    t._elem = null;
+                  }
+                }
+                t = null;
+
+                delete axis.ticks;
+                delete axis._ticks;
+                this.axes[name] = new Axis(name);
+                this.axes[name]._plotWidth = this._width;
+                this.axes[name]._plotHeight = this._height;
+            }
+            
+            if (data) {
+                if (options.dataRenderer && $.isFunction(options.dataRenderer)) {
+                    if (options.dataRendererOptions) {
+                        this.dataRendererOptions = options.dataRendererOptions;
+                    }
+                    this.dataRenderer = options.dataRenderer;
+                    data = this.dataRenderer(data, this, this.dataRendererOptions);
+                }
+                
+                // make a copy of the data
+                this.data = $.extend(true, [], data);
+            }
+
+            if (opts) {
+                this.parseOptions(options);
+            }
+            
+            this.title._plotWidth = this._width;
+            
+            if (this.textColor) {
+                this.target.css('color', this.textColor);
+            }
+            if (this.fontFamily) {
+                this.target.css('font-family', this.fontFamily);
+            }
+            if (this.fontSize) {
+                this.target.css('font-size', this.fontSize);
+            }
+
+            this.title.init();
+            this.legend.init();
+            this._sumy = 0;
+            this._sumx = 0;
+
+            this.seriesStack = [];
+            this.previousSeriesStack = [];
+
+            this.computePlotData();
+            for (var i=0, l=this.series.length; i<l; i++) {
+                // set default stacking order for series canvases
+                this.seriesStack.push(i);
+                this.previousSeriesStack.push(i);
+                this.series[i].shadowCanvas._plotDimensions = this._plotDimensions;
+                this.series[i].canvas._plotDimensions = this._plotDimensions;
+                for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) {
+                    $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+                }
+                for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) {
+                    this.preSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+                }
+                // this.populatePlotData(this.series[i], i);
+                this.series[i]._plotDimensions = this._plotDimensions;
+                this.series[i].init(i, this.grid.borderWidth, this);
+                for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) {
+                    $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+                }
+                for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) {
+                    this.postSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
+                }
+                this._sumy += this.series[i]._sumy;
+                this._sumx += this.series[i]._sumx;
+            }
+
+            for (var i=0, l=_axisNames.length; i<l; i++) {
+                name = _axisNames[i];
+                axis = this.axes[name];
+
+                axis._plotDimensions = this._plotDimensions;
+                axis.init();
+                if (axis.borderColor == null) {
+                    if (name.charAt(0) !== 'x' && axis.useSeriesColor === true && axis.show) {
+                        axis.borderColor = axis._series[0].color;
+                    }
+                    else {
+                        axis.borderColor = this.grid.borderColor;
+                    }
+                }
+            }
+            
+            if (this.sortData) {
+                sortData(this.series);
+            }
+            this.grid.init();
+            this.grid._axes = this.axes;
+            
+            this.legend._series = this.series;
+
+            for (var i=0, l=$.jqplot.postInitHooks.length; i<l; i++) {
+                $.jqplot.postInitHooks[i].call(this, target, this.data, options);
+            }
+
+            for (var i=0, l=this.postInitHooks.hooks.length; i<l; i++) {
+                this.postInitHooks.hooks[i].call(this, target, this.data, options);
+            }
+        };
+
+
+
+        // method: quickInit
+        // 
+        // Quick reinitialization plot for replotting.
+        // Does not parse options ore recreate axes and series.
+        // not called directly.
+        this.quickInit = function () {
+            // Plot should be visible and have a height and width.
+            // If plot doesn't have height and width for some
+            // reason, set it by other means.  Plot must not have
+            // a display:none attribute, however.
+            
+            this._height = this.target.height();
+            this._width = this.target.width();
+            
+            if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
+                throw new Error("Target dimension not set");
+            }
+            
+            this._plotDimensions.height = this._height;
+            this._plotDimensions.width = this._width;
+            this.grid._plotDimensions = this._plotDimensions;
+            this.title._plotDimensions = this._plotDimensions;
+            this.baseCanvas._plotDimensions = this._plotDimensions;
+            this.eventCanvas._plotDimensions = this._plotDimensions;
+            this.legend._plotDimensions = this._plotDimensions;
+            
+            for (var n in this.axes) {
+                this.axes[n]._plotWidth = this._width;
+                this.axes[n]._plotHeight = this._height;
+            }
+            
+            this.title._plotWidth = this._width;
+            
+            if (this.textColor) {
+                this.target.css('color', this.textColor);
+            }
+            if (this.fontFamily) {
+                this.target.css('font-family', this.fontFamily);
+            }
+            if (this.fontSize) {
+                this.target.css('font-size', this.fontSize);
+            }
+            
+            this._sumy = 0;
+            this._sumx = 0;
+            this.computePlotData();
+            for (var i=0; i<this.series.length; i++) {
+                // this.populatePlotData(this.series[i], i);
+                if (this.series[i]._type === 'line' && this.series[i].renderer.bands.show) {
+                    this.series[i].renderer.initBands.call(this.series[i], this.series[i].renderer.options, this);
+                }
+                this.series[i]._plotDimensions = this._plotDimensions;
+                this.series[i].canvas._plotDimensions = this._plotDimensions;
+                //this.series[i].init(i, this.grid.borderWidth);
+                this._sumy += this.series[i]._sumy;
+                this._sumx += this.series[i]._sumx;
+            }
+
+            var name;
+            
+            for (var j=0; j<12; j++) {
+                name = _axisNames[j];
+                // Memory Leaks patch : clear ticks elements
+                var t = this.axes[name]._ticks;
+                for (var i = 0; i < t.length; i++) {
+                  var el = t[i]._elem;
+                  if (el) {
+                    // if canvas renderer
+                    if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+                      window.G_vmlCanvasManager.uninitElement(el.get(0));
+                    }
+                    el.emptyForce();
+                    el = null;
+                    t._elem = null;
+                  }
+                }
+                t = null;
+                
+                this.axes[name]._plotDimensions = this._plotDimensions;
+                this.axes[name]._ticks = [];
+                // this.axes[name].renderer.init.call(this.axes[name], {});
+            }
+            
+            if (this.sortData) {
+                sortData(this.series);
+            }
+            
+            this.grid._axes = this.axes;
+            
+            this.legend._series = this.series;
+        };
+        
+        // sort the series data in increasing order.
+        function sortData(series) {
+            var d, sd, pd, ppd, ret;
+            for (var i=0; i<series.length; i++) {
+                var check;
+                var bat = [series[i].data, series[i]._stackData, series[i]._plotData, series[i]._prevPlotData];
+                for (var n=0; n<4; n++) {
+                    check = true;
+                    d = bat[n];
+                    if (series[i]._stackAxis == 'x') {
+                        for (var j = 0; j < d.length; j++) {
+                            if (typeof(d[j][1]) != "number") {
+                                check = false;
+                                break;
+                            }
+                        }
+                        if (check) {
+                            d.sort(function(a,b) { return a[1] - b[1]; });
+                        }
+                    }
+                    else {
+                        for (var j = 0; j < d.length; j++) {
+                            if (typeof(d[j][0]) != "number") {
+                                check = false;
+                                break;
+                            }
+                        }
+                        if (check) {
+                            d.sort(function(a,b) { return a[0] - b[0]; });
+                        }
+                    }
+                }
+               
+            }
+        }
+
+        this.computePlotData = function() {
+            this._plotData = [];
+            this._stackData = [];
+            var series,
+                index,
+                l;
+
+
+            for (index=0, l=this.series.length; index<l; index++) {
+                series = this.series[index];
+                this._plotData.push([]);
+                this._stackData.push([]);
+                var cd = series.data;
+                this._plotData[index] = $.extend(true, [], cd);
+                this._stackData[index] = $.extend(true, [], cd);
+                series._plotData = this._plotData[index];
+                series._stackData = this._stackData[index];
+                var plotValues = {x:[], y:[]};
+
+                if (this.stackSeries && !series.disableStack) {
+                    series._stack = true;
+                    ///////////////////////////
+                    // have to check for nulls
+                    ///////////////////////////
+                    var sidx = (series._stackAxis === 'x') ? 0 : 1;
+
+                    for (var k=0, cdl=cd.length; k<cdl; k++) {
+                        var temp = cd[k][sidx];
+                        if (temp == null) {
+                            temp = 0;
+                        }
+                        this._plotData[index][k][sidx] = temp;
+                        this._stackData[index][k][sidx] = temp;
+
+                        if (index > 0) {
+                            for (var j=index; j--;) {
+                                var prevval = this._plotData[j][k][sidx];
+                                // only need to sum up the stack axis column of data
+                                // and only sum if it is of same sign.
+                                // if previous series isn't same sign, keep looking
+                                // at earlier series untill we find one of same sign.
+                                if (temp * prevval >= 0) {
+                                    this._plotData[index][k][sidx] += prevval;
+                                    this._stackData[index][k][sidx] += prevval;
+                                    break;
+                                } 
+                            }
+                        }
+                    }
+
+                }
+                else {
+                    for (var i=0; i<series.data.length; i++) {
+                        plotValues.x.push(series.data[i][0]);
+                        plotValues.y.push(series.data[i][1]);
+                    }
+                    this._stackData.push(series.data);
+                    this.series[index]._stackData = series.data;
+                    this._plotData.push(series.data);
+                    series._plotData = series.data;
+                    series._plotValues = plotValues;
+                }
+                if (index>0) {
+                    series._prevPlotData = this.series[index-1]._plotData;
+                }
+                series._sumy = 0;
+                series._sumx = 0;
+                for (i=series.data.length-1; i>-1; i--) {
+                    series._sumy += series.data[i][1];
+                    series._sumx += series.data[i][0];
+                }
+            }
+
+        };
+        
+        // populate the _stackData and _plotData arrays for the plot and the series.
+        this.populatePlotData = function(series, index) {
+            // if a stacked chart, compute the stacked data
+            this._plotData = [];
+            this._stackData = [];
+            series._stackData = [];
+            series._plotData = [];
+            var plotValues = {x:[], y:[]};
+            if (this.stackSeries && !series.disableStack) {
+                series._stack = true;
+                var sidx = (series._stackAxis === 'x') ? 0 : 1;
+                // var idx = sidx ? 0 : 1;
+                // push the current data into stackData
+                //this._stackData.push(this.series[i].data);
+                var temp = $.extend(true, [], series.data);
+                // create the data that will be plotted for this series
+                var plotdata = $.extend(true, [], series.data);
+                var tempx, tempy, dval, stackval, comparator;
+                // for first series, nothing to add to stackData.
+                for (var j=0; j<index; j++) {
+                    var cd = this.series[j].data;
+                    for (var k=0; k<cd.length; k++) {
+                        dval = cd[k];
+                        tempx = (dval[0] != null) ? dval[0] : 0;
+                        tempy = (dval[1] != null) ? dval[1] : 0;
+                        temp[k][0] += tempx;
+                        temp[k][1] += tempy;
+                        stackval = (sidx) ? tempy : tempx;
+                        // only need to sum up the stack axis column of data
+                        // and only sum if it is of same sign.
+                        if (series.data[k][sidx] * stackval >= 0) {
+                            plotdata[k][sidx] += stackval;
+                        }
+                    }
+                }
+                for (var i=0; i<plotdata.length; i++) {
+                    plotValues.x.push(plotdata[i][0]);
+                    plotValues.y.push(plotdata[i][1]);
+                }
+                this._plotData.push(plotdata);
+                this._stackData.push(temp);
+                series._stackData = temp;
+                series._plotData = plotdata;
+                series._plotValues = plotValues;
+            }
+            else {
+                for (var i=0; i<series.data.length; i++) {
+                    plotValues.x.push(series.data[i][0]);
+                    plotValues.y.push(series.data[i][1]);
+                }
+                this._stackData.push(series.data);
+                this.series[index]._stackData = series.data;
+                this._plotData.push(series.data);
+                series._plotData = series.data;
+                series._plotValues = plotValues;
+            }
+            if (index>0) {
+                series._prevPlotData = this.series[index-1]._plotData;
+            }
+            series._sumy = 0;
+            series._sumx = 0;
+            for (i=series.data.length-1; i>-1; i--) {
+                series._sumy += series.data[i][1];
+                series._sumx += series.data[i][0];
+            }
+        };
+        
+        // function to safely return colors from the color array and wrap around at the end.
+        this.getNextSeriesColor = (function(t) {
+            var idx = 0;
+            var sc = t.seriesColors;
+            
+            return function () { 
+                if (idx < sc.length) {
+                    return sc[idx++];
+                }
+                else {
+                    idx = 0;
+                    return sc[idx++];
+                }
+            };
+        })(this);
+    
+        this.parseOptions = function(options){
+            for (var i=0; i<this.preParseOptionsHooks.hooks.length; i++) {
+                this.preParseOptionsHooks.hooks[i].call(this, options);
+            }
+            for (var i=0; i<$.jqplot.preParseOptionsHooks.length; i++) {
+                $.jqplot.preParseOptionsHooks[i].call(this, options);
+            }
+            this.options = $.extend(true, {}, this.defaults, options);
+            var opts = this.options;
+            this.animate = opts.animate;
+            this.animateReplot = opts.animateReplot;
+            this.stackSeries = opts.stackSeries;
+            if ($.isPlainObject(opts.fillBetween)) {
+
+                var temp = ['series1', 'series2', 'color', 'baseSeries', 'fill'], 
+                    tempi;
+
+                for (var i=0, l=temp.length; i<l; i++) {
+                    tempi = temp[i];
+                    if (opts.fillBetween[tempi] != null) {
+                        this.fillBetween[tempi] = opts.fillBetween[tempi];
+                    }
+                }
+            }
+
+            if (opts.seriesColors) {
+                this.seriesColors = opts.seriesColors;
+            }
+            if (opts.negativeSeriesColors) {
+                this.negativeSeriesColors = opts.negativeSeriesColors;
+            }
+            if (opts.captureRightClick) {
+                this.captureRightClick = opts.captureRightClick;
+            }
+            this.defaultAxisStart = (options && options.defaultAxisStart != null) ? options.defaultAxisStart : this.defaultAxisStart;
+            this.colorGenerator.setColors(this.seriesColors);
+            this.negativeColorGenerator.setColors(this.negativeSeriesColors);
+            // var cg = new this.colorGenerator(this.seriesColors);
+            // var ncg = new this.colorGenerator(this.negativeSeriesColors);
+            // this._gridPadding = this.options.gridPadding;
+            $.extend(true, this._gridPadding, opts.gridPadding);
+            this.sortData = (opts.sortData != null) ? opts.sortData : this.sortData;
+            for (var i=0; i<12; i++) {
+                var n = _axisNames[i];
+                var axis = this.axes[n];
+                axis._options = $.extend(true, {}, opts.axesDefaults, opts.axes[n]);
+                $.extend(true, axis, opts.axesDefaults, opts.axes[n]);
+                axis._plotWidth = this._width;
+                axis._plotHeight = this._height;
+            }
+            // if (this.data.length == 0) {
+            //     this.data = [];
+            //     for (var i=0; i<this.options.series.length; i++) {
+            //         this.data.push(this.options.series.data);
+            //     }    
+            // }
+                
+            var normalizeData = function(data, dir, start) {
+                // return data as an array of point arrays,
+                // in form [[x1,y1...], [x2,y2...], ...]
+                var temp = [];
+                var i, l;
+                dir = dir || 'vertical';
+                if (!$.isArray(data[0])) {
+                    // we have a series of scalars.  One line with just y values.
+                    // turn the scalar list of data into a data array of form:
+                    // [[1, data[0]], [2, data[1]], ...]
+                    for (i=0, l=data.length; i<l; i++) {
+                        if (dir == 'vertical') {
+                            temp.push([start + i, data[i]]);   
+                        }
+                        else {
+                            temp.push([data[i], start+i]);
+                        }
+                    }
+                }            
+                else {
+                    // we have a properly formatted data series, copy it.
+                    $.extend(true, temp, data);
+                }
+                return temp;
+            };
+
+            var colorIndex = 0;
+            this.series = [];
+            for (var i=0; i<this.data.length; i++) {
+                var sopts = $.extend(true, {index: i}, {seriesColors:this.seriesColors, negativeSeriesColors:this.negativeSeriesColors}, this.options.seriesDefaults, this.options.series[i], {rendererOptions:{animation:{show: this.animate}}});
+                // pass in options in case something needs set prior to initialization.
+                var temp = new Series(sopts);
+                for (var j=0; j<$.jqplot.preParseSeriesOptionsHooks.length; j++) {
+                    $.jqplot.preParseSeriesOptionsHooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]);
+                }
+                for (var j=0; j<this.preParseSeriesOptionsHooks.hooks.length; j++) {
+                    this.preParseSeriesOptionsHooks.hooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]);
+                }
+                // Now go back and apply the options to the series.  Really should just do this during initializaiton, but don't want to
+                // mess up preParseSeriesOptionsHooks at this point.
+                $.extend(true, temp, sopts);
+                var dir = 'vertical';
+                if (temp.renderer === $.jqplot.BarRenderer && temp.rendererOptions && temp.rendererOptions.barDirection == 'horizontal') {
+                    dir = 'horizontal';
+                    temp._stackAxis = 'x';
+                    temp._primaryAxis = '_yaxis';
+                }
+                temp.data = normalizeData(this.data[i], dir, this.defaultAxisStart);
+                switch (temp.xaxis) {
+                    case 'xaxis':
+                        temp._xaxis = this.axes.xaxis;
+                        break;
+                    case 'x2axis':
+                        temp._xaxis = this.axes.x2axis;
+                        break;
+                    default:
+                        break;
+                }
+                temp._yaxis = this.axes[temp.yaxis];
+                temp._xaxis._series.push(temp);
+                temp._yaxis._series.push(temp);
+                if (temp.show) {
+                    temp._xaxis.show = true;
+                    temp._yaxis.show = true;
+                }
+                else {
+                    if (temp._xaxis.scaleToHiddenSeries) {
+                        temp._xaxis.show = true;
+                    }
+                    if (temp._yaxis.scaleToHiddenSeries) {
+                        temp._yaxis.show = true;
+                    }
+                }
+
+                // // parse the renderer options and apply default colors if not provided
+                // if (!temp.color && temp.show != false) {
+                //     temp.color = cg.next();
+                //     colorIndex = cg.getIndex() - 1;;
+                // }
+                // if (!temp.negativeColor && temp.show != false) {
+                //     temp.negativeColor = ncg.get(colorIndex);
+                //     ncg.setIndex(colorIndex);
+                // }
+                if (!temp.label) {
+                    temp.label = 'Series '+ (i+1).toString();
+                }
+                // temp.rendererOptions.show = temp.show;
+                // $.extend(true, temp.renderer, {color:this.seriesColors[i]}, this.rendererOptions);
+                this.series.push(temp);  
+                for (var j=0; j<$.jqplot.postParseSeriesOptionsHooks.length; j++) {
+                    $.jqplot.postParseSeriesOptionsHooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]);
+                }
+                for (var j=0; j<this.postParseSeriesOptionsHooks.hooks.length; j++) {
+                    this.postParseSeriesOptionsHooks.hooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]);
+                }
+            }
+            
+            // copy the grid and title options into this object.
+            $.extend(true, this.grid, this.options.grid);
+            // if axis border properties aren't set, set default.
+            for (var i=0, l=_axisNames.length; i<l; i++) {
+                var n = _axisNames[i];
+                var axis = this.axes[n];
+                if (axis.borderWidth == null) {
+                    axis.borderWidth =this.grid.borderWidth;
+                }
+            }
+            
+            if (typeof this.options.title == 'string') {
+                this.title.text = this.options.title;
+            }
+            else if (typeof this.options.title == 'object') {
+                $.extend(true, this.title, this.options.title);
+            }
+            this.title._plotWidth = this._width;
+            this.legend.setOptions(this.options.legend);
+            
+            for (var i=0; i<$.jqplot.postParseOptionsHooks.length; i++) {
+                $.jqplot.postParseOptionsHooks[i].call(this, options);
+            }
+            for (var i=0; i<this.postParseOptionsHooks.hooks.length; i++) {
+                this.postParseOptionsHooks.hooks[i].call(this, options);
+            }
+        };
+        
+        // method: destroy
+        // Releases all resources occupied by the plot
+        this.destroy = function() {
+            this.canvasManager.freeAllCanvases();
+            if (this.eventCanvas && this.eventCanvas._elem) {
+                this.eventCanvas._elem.unbind();
+            }
+            // Couple of posts on Stack Overflow indicate that empty() doesn't
+            // always cear up the dom and release memory.  Sometimes setting
+            // innerHTML property to null is needed.  Particularly on IE, may 
+            // have to directly set it to null, bypassing $.
+            this.target.empty();
+
+            this.target[0].innerHTML = '';
+        };
+        
+        // method: replot
+        // Does a reinitialization of the plot followed by
+        // a redraw.  Method could be used to interactively
+        // change plot characteristics and then replot.
+        //
+        // Parameters:
+        // options - Options used for replotting.
+        //
+        // Properties:
+        // clear - false to not clear (empty) the plot container before replotting (default: true).
+        // resetAxes - true to reset all axes min, max, numberTicks and tickInterval setting so axes will rescale themselves.
+        //             optionally pass in list of axes to reset (e.g. ['xaxis', 'y2axis']) (default: false).
+        this.replot = function(options) {
+            var opts =  options || {};
+            var data = opts.data || null;
+            var clear = (opts.clear === false) ? false : true;
+            var resetAxes = opts.resetAxes || false;
+            delete opts.data;
+            delete opts.clear;
+            delete opts.resetAxes;
+
+            this.target.trigger('jqplotPreReplot');
+            
+            if (clear) {
+                this.destroy();
+            }
+            // if have data or other options, full reinit.
+            // otherwise, quickinit.
+            if (data || !$.isEmptyObject(opts)) {
+                this.reInitialize(data, opts);
+            }
+            else {
+                this.quickInit();
+            }
+
+            if (resetAxes) {
+                this.resetAxesScale(resetAxes, opts.axes);
+            }
+            this.draw();
+            this.target.trigger('jqplotPostReplot');
+        };
+        
+        // method: redraw
+        // Empties the plot target div and redraws the plot.
+        // This enables plot data and properties to be changed
+        // and then to comletely clear the plot and redraw.
+        // redraw *will not* reinitialize any plot elements.
+        // That is, axes will not be autoscaled and defaults
+        // will not be reapplied to any plot elements.  redraw
+        // is used primarily with zooming. 
+        //
+        // Parameters:
+        // clear - false to not clear (empty) the plot container before redrawing (default: true).
+        this.redraw = function(clear) {
+            clear = (clear != null) ? clear : true;
+            this.target.trigger('jqplotPreRedraw');
+            if (clear) {
+                this.canvasManager.freeAllCanvases();
+                this.eventCanvas._elem.unbind();
+                // Dont think I bind any events to the target, this shouldn't be necessary.
+                // It will remove user's events.
+                // this.target.unbind();
+                this.target.empty();
+            }
+             for (var ax in this.axes) {
+                this.axes[ax]._ticks = [];
+            }
+            this.computePlotData();
+            // for (var i=0; i<this.series.length; i++) {
+            //     this.populatePlotData(this.series[i], i);
+            // }
+            this._sumy = 0;
+            this._sumx = 0;
+            for (var i=0, tsl = this.series.length; i<tsl; i++) {
+                this._sumy += this.series[i]._sumy;
+                this._sumx += this.series[i]._sumx;
+            }
+            this.draw();
+            this.target.trigger('jqplotPostRedraw');
+        };
+        
+        // method: draw
+        // Draws all elements of the plot into the container.
+        // Does not clear the container before drawing.
+        this.draw = function(){
+            if (this.drawIfHidden || this.target.is(':visible')) {
+                this.target.trigger('jqplotPreDraw');
+                var i,
+                    j,
+                    l,
+                    tempseries;
+                for (i=0, l=$.jqplot.preDrawHooks.length; i<l; i++) {
+                    $.jqplot.preDrawHooks[i].call(this);
+                }
+                for (i=0, l=this.preDrawHooks.hooks.length; i<l; i++) {
+                    this.preDrawHooks.hooks[i].apply(this, this.preDrawSeriesHooks.args[i]);
+                }
+                // create an underlying canvas to be used for special features.
+                this.target.append(this.baseCanvas.createElement({left:0, right:0, top:0, bottom:0}, 'jqplot-base-canvas', null, this));
+                this.baseCanvas.setContext();
+                this.target.append(this.title.draw());
+                this.title.pack({top:0, left:0});
+                
+                // make room  for the legend between the grid and the edge.
+                // pass a dummy offsets object and a reference to the plot.
+                var legendElem = this.legend.draw({}, this);
+                
+                var gridPadding = {top:0, left:0, bottom:0, right:0};
+                
+                if (this.legend.placement == "outsideGrid") {
+                    // temporarily append the legend to get dimensions
+                    this.target.append(legendElem);
+                    switch (this.legend.location) {
+                        case 'n':
+                            gridPadding.top += this.legend.getHeight();
+                            break;
+                        case 's':
+                            gridPadding.bottom += this.legend.getHeight();
+                            break;
+                        case 'ne':
+                        case 'e':
+                        case 'se':
+                            gridPadding.right += this.legend.getWidth();
+                            break;
+                        case 'nw':
+                        case 'w':
+                        case 'sw':
+                            gridPadding.left += this.legend.getWidth();
+                            break;
+                        default:  // same as 'ne'
+                            gridPadding.right += this.legend.getWidth();
+                            break;
+                    }
+                    legendElem = legendElem.detach();
+                }
+                
+                var ax = this.axes;
+                var name;
+                // draw the yMidAxis first, so xaxis of pyramid chart can adjust itself if needed.
+                for (i=0; i<12; i++) {
+                    name = _axisNames[i];
+                    this.target.append(ax[name].draw(this.baseCanvas._ctx, this));
+                    ax[name].set();
+                }
+                if (ax.yaxis.show) {
+                    gridPadding.left += ax.yaxis.getWidth();
+                }
+                var ra = ['y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis'];
+                var rapad = [0, 0, 0, 0, 0, 0, 0, 0];
+                var gpr = 0;
+                var n;
+                for (n=0; n<8; n++) {
+                    if (ax[ra[n]].show) {
+                        gpr += ax[ra[n]].getWidth();
+                        rapad[n] = gpr;
+                    }
+                }
+                gridPadding.right += gpr;
+                if (ax.x2axis.show) {
+                    gridPadding.top += ax.x2axis.getHeight();
+                }
+                if (this.title.show) {
+                    gridPadding.top += this.title.getHeight();
+                }
+                if (ax.xaxis.show) {
+                    gridPadding.bottom += ax.xaxis.getHeight();
+                }
+                
+                // end of gridPadding adjustments.
+
+                // if user passed in gridDimensions option, check against calculated gridPadding
+                if (this.options.gridDimensions && $.isPlainObject(this.options.gridDimensions)) {
+                    var gdw = parseInt(this.options.gridDimensions.width, 10) || 0;
+                    var gdh = parseInt(this.options.gridDimensions.height, 10) || 0;
+                    var widthAdj = (this._width - gridPadding.left - gridPadding.right - gdw)/2;
+                    var heightAdj = (this._height - gridPadding.top - gridPadding.bottom - gdh)/2;
+
+                    if (heightAdj >= 0 && widthAdj >= 0) {
+                        gridPadding.top += heightAdj;
+                        gridPadding.bottom += heightAdj;
+                        gridPadding.left += widthAdj;
+                        gridPadding.right += widthAdj;
+                    }
+                }
+                var arr = ['top', 'bottom', 'left', 'right'];
+                for (var n in arr) {
+                    if (this._gridPadding[arr[n]] == null && gridPadding[arr[n]] > 0) {
+                        this._gridPadding[arr[n]] = gridPadding[arr[n]];
+                    }
+                    else if (this._gridPadding[arr[n]] == null) {
+                        this._gridPadding[arr[n]] = this._defaultGridPadding[arr[n]];
+                    }
+                }
+                
+                var legendPadding = this._gridPadding;
+                
+                if (this.legend.placement === 'outsideGrid') {
+                    legendPadding = {top:this.title.getHeight(), left: 0, right: 0, bottom: 0};
+                    if (this.legend.location === 's') {
+                        legendPadding.left = this._gridPadding.left;
+                        legendPadding.right = this._gridPadding.right;
+                    }
+                }
+                
+                ax.xaxis.pack({position:'absolute', bottom:this._gridPadding.bottom - ax.xaxis.getHeight(), left:0, width:this._width}, {min:this._gridPadding.left, max:this._width - this._gridPadding.right});
+                ax.yaxis.pack({position:'absolute', top:0, left:this._gridPadding.left - ax.yaxis.getWidth(), height:this._height}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top});
+                ax.x2axis.pack({position:'absolute', top:this._gridPadding.top - ax.x2axis.getHeight(), left:0, width:this._width}, {min:this._gridPadding.left, max:this._width - this._gridPadding.right});
+                for (i=8; i>0; i--) {
+                    ax[ra[i-1]].pack({position:'absolute', top:0, right:this._gridPadding.right - rapad[i-1]}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top});
+                }
+                var ltemp = (this._width - this._gridPadding.left - this._gridPadding.right)/2.0 + this._gridPadding.left - ax.yMidAxis.getWidth()/2.0;
+                ax.yMidAxis.pack({position:'absolute', top:0, left:ltemp, zIndex:9, textAlign: 'center'}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top});
+            
+                this.target.append(this.grid.createElement(this._gridPadding, this));
+                this.grid.draw();
+                
+                var series = this.series;
+                var seriesLength = series.length;
+                // put the shadow canvases behind the series canvases so shadows don't overlap on stacked bars.
+                for (i=0, l=seriesLength; i<l; i++) {
+                    // draw series in order of stacking.  This affects only
+                    // order in which canvases are added to dom.
+                    j = this.seriesStack[i];
+                    this.target.append(series[j].shadowCanvas.createElement(this._gridPadding, 'jqplot-series-shadowCanvas', null, this));
+                    series[j].shadowCanvas.setContext();
+                    series[j].shadowCanvas._elem.data('seriesIndex', j);
+                }
+                
+                for (i=0, l=seriesLength; i<l; i++) {
+                    // draw series in order of stacking.  This affects only
+                    // order in which canvases are added to dom.
+                    j = this.seriesStack[i];
+                    this.target.append(series[j].canvas.createElement(this._gridPadding, 'jqplot-series-canvas', null, this));
+                    series[j].canvas.setContext();
+                    series[j].canvas._elem.data('seriesIndex', j);
+                }
+                // Need to use filled canvas to capture events in IE.
+                // Also, canvas seems to block selection of other elements in document on FF.
+                this.target.append(this.eventCanvas.createElement(this._gridPadding, 'jqplot-event-canvas', null, this));
+                this.eventCanvas.setContext();
+                this.eventCanvas._ctx.fillStyle = 'rgba(0,0,0,0)';
+                this.eventCanvas._ctx.fillRect(0,0,this.eventCanvas._ctx.canvas.width, this.eventCanvas._ctx.canvas.height);
+            
+                // bind custom event handlers to regular events.
+                this.bindCustomEvents();
+            
+                // draw legend before series if the series needs to know the legend dimensions.
+                if (this.legend.preDraw) {  
+                    this.eventCanvas._elem.before(legendElem);
+                    this.legend.pack(legendPadding);
+                    if (this.legend._elem) {
+                        this.drawSeries({legendInfo:{location:this.legend.location, placement:this.legend.placement, width:this.legend.getWidth(), height:this.legend.getHeight(), xoffset:this.legend.xoffset, yoffset:this.legend.yoffset}});
+                    }
+                    else {
+                        this.drawSeries();
+                    }
+                }
+                else {  // draw series before legend
+                    this.drawSeries();
+                    if (seriesLength) {
+                        $(series[seriesLength-1].canvas._elem).after(legendElem);
+                    }
+                    this.legend.pack(legendPadding);                
+                }
+            
+                // register event listeners on the overlay canvas
+                for (var i=0, l=$.jqplot.eventListenerHooks.length; i<l; i++) {
+                    // in the handler, this will refer to the eventCanvas dom element.
+                    // make sure there are references back into plot objects.
+                    this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]);
+                }
+            
+                // register event listeners on the overlay canvas
+                for (var i=0, l=this.eventListenerHooks.hooks.length; i<l; i++) {
+                    // in the handler, this will refer to the eventCanvas dom element.
+                    // make sure there are references back into plot objects.
+                    this.eventCanvas._elem.bind(this.eventListenerHooks.hooks[i][0], {plot:this}, this.eventListenerHooks.hooks[i][1]);
+                }
+
+                var fb = this.fillBetween;
+                if (fb.fill && fb.series1 !== fb.series2 && fb.series1 < seriesLength && fb.series2 < seriesLength && series[fb.series1]._type === 'line' && series[fb.series2]._type === 'line') {
+                    this.doFillBetweenLines();
+                }
+
+                for (var i=0, l=$.jqplot.postDrawHooks.length; i<l; i++) {
+                    $.jqplot.postDrawHooks[i].call(this);
+                }
+
+                for (var i=0, l=this.postDrawHooks.hooks.length; i<l; i++) {
+                    this.postDrawHooks.hooks[i].apply(this, this.postDrawHooks.args[i]);
+                }
+            
+                if (this.target.is(':visible')) {
+                    this._drawCount += 1;
+                }
+
+                var temps, 
+                    tempr,
+                    sel,
+                    _els;
+                // ughh.  ideally would hide all series then show them.
+                for (i=0, l=seriesLength; i<l; i++) {
+                    temps = series[i];
+                    tempr = temps.renderer;
+                    sel = '.jqplot-point-label.jqplot-series-'+i;
+                    if (tempr.animation && tempr.animation._supported && tempr.animation.show && (this._drawCount < 2 || this.animateReplot)) {
+                        _els = this.target.find(sel);
+                        _els.stop(true, true).hide();
+                        temps.canvas._elem.stop(true, true).hide();
+                        temps.shadowCanvas._elem.stop(true, true).hide();
+                        temps.canvas._elem.jqplotEffect('blind', {mode: 'show', direction: tempr.animation.direction}, tempr.animation.speed);
+                        temps.shadowCanvas._elem.jqplotEffect('blind', {mode: 'show', direction: tempr.animation.direction}, tempr.animation.speed);
+                        _els.fadeIn(tempr.animation.speed*0.8);
+                    }
+                }
+                _els = null;
+            
+                this.target.trigger('jqplotPostDraw', [this]);
+            }
+        };
+
+        jqPlot.prototype.doFillBetweenLines = function () {
+            var fb = this.fillBetween;
+            var sid1 = fb.series1;
+            var sid2 = fb.series2;
+            // first series should always be lowest index
+            var id1 = (sid1 < sid2) ? sid1 : sid2;
+            var id2 = (sid2 >  sid1) ? sid2 : sid1;
+
+            var series1 = this.series[id1];
+            var series2 = this.series[id2];
+
+            if (series2.renderer.smooth) {
+                var tempgd = series2.renderer._smoothedData.slice(0).reverse();
+            }
+            else {
+                var tempgd = series2.gridData.slice(0).reverse();
+            }
+
+            if (series1.renderer.smooth) {
+                var gd = series1.renderer._smoothedData.concat(tempgd);
+            }
+            else {
+                var gd = series1.gridData.concat(tempgd);
+            }
+
+            var color = (fb.color !== null) ? fb.color : this.series[sid1].fillColor;
+            var baseSeries = (fb.baseSeries !== null) ? fb.baseSeries : id1;
+
+            // now apply a fill to the shape on the lower series shadow canvas,
+            // so it is behind both series.
+            var sr = this.series[baseSeries].renderer.shapeRenderer;
+            var opts = {fillStyle: color, fill: true, closePath: true};
+            sr.draw(series1.shadowCanvas._ctx, gd, opts);
+        };
+        
+        this.bindCustomEvents = function() {
+            this.eventCanvas._elem.bind('click', {plot:this}, this.onClick);
+            this.eventCanvas._elem.bind('dblclick', {plot:this}, this.onDblClick);
+            this.eventCanvas._elem.bind('mousedown', {plot:this}, this.onMouseDown);
+            this.eventCanvas._elem.bind('mousemove', {plot:this}, this.onMouseMove);
+            this.eventCanvas._elem.bind('mouseenter', {plot:this}, this.onMouseEnter);
+            this.eventCanvas._elem.bind('mouseleave', {plot:this}, this.onMouseLeave);
+            if (this.captureRightClick) {
+                this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onRightClick);
+                this.eventCanvas._elem.get(0).oncontextmenu = function() {
+                    return false;
+                };
+            }
+            else {
+                this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onMouseUp);
+            }
+        };
+        
+        function getEventPosition(ev) {
+            var plot = ev.data.plot;
+            var go = plot.eventCanvas._elem.offset();
+            var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top};
+            var dataPos = {xaxis:null, yaxis:null, x2axis:null, y2axis:null, y3axis:null, y4axis:null, y5axis:null, y6axis:null, y7axis:null, y8axis:null, y9axis:null, yMidAxis:null};
+            var an = ['xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
+            var ax = plot.axes;
+            var n, axis;
+            for (n=11; n>0; n--) {
+                axis = an[n-1];
+                if (ax[axis].show) {
+                    dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]);
+                }
+            }
+
+            return {offsets:go, gridPos:gridPos, dataPos:dataPos};
+        }
+        
+        
+        // function to check if event location is over a area area
+        function checkIntersection(gridpos, plot) {
+            var series = plot.series;
+            var i, j, k, s, r, x, y, theta, sm, sa, minang, maxang;
+            var d0, d, p, pp, points, bw, hp;
+            var threshold, t;
+            for (k=plot.seriesStack.length-1; k>=0; k--) {
+                i = plot.seriesStack[k];
+                s = series[i];
+                hp = s._highlightThreshold;
+                switch (s.renderer.constructor) {
+                    case $.jqplot.BarRenderer:
+                        x = gridpos.x;
+                        y = gridpos.y;
+                        for (j=0; j<s._barPoints.length; j++) {
+                            points = s._barPoints[j];
+                            p = s.gridData[j];
+                            if (x>points[0][0] && x<points[2][0] && y>points[2][1] && y<points[0][1]) {
+                                return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]};
+                            }
+                        }
+                        break;
+                    case $.jqplot.PyramidRenderer:
+                        x = gridpos.x;
+                        y = gridpos.y;
+                        for (j=0; j<s._barPoints.length; j++) {
+                            points = s._barPoints[j];
+                            p = s.gridData[j];
+                            if (x > points[0][0] + hp[0][0] && x < points[2][0] + hp[2][0] && y > points[2][1] && y < points[0][1]) {
+                                return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]};
+                            }
+                        }
+                        break;
+                    
+                    case $.jqplot.DonutRenderer:
+                        sa = s.startAngle/180*Math.PI;
+                        x = gridpos.x - s._center[0];
+                        y = gridpos.y - s._center[1];
+                        r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
+                        if (x > 0 && -y >= 0) {
+                            theta = 2*Math.PI - Math.atan(-y/x);
+                        }
+                        else if (x > 0 && -y < 0) {
+                            theta = -Math.atan(-y/x);
+                        }
+                        else if (x < 0) {
+                            theta = Math.PI - Math.atan(-y/x);
+                        }
+                        else if (x == 0 && -y > 0) {
+                            theta = 3*Math.PI/2;
+                        }
+                        else if (x == 0 && -y < 0) {
+                            theta = Math.PI/2;
+                        }
+                        else if (x == 0 && y == 0) {
+                            theta = 0;
+                        }
+                        if (sa) {
+                            theta -= sa;
+                            if (theta < 0) {
+                                theta += 2*Math.PI;
+                            }
+                            else if (theta > 2*Math.PI) {
+                                theta -= 2*Math.PI;
+                            }
+                        }
+            
+                        sm = s.sliceMargin/180*Math.PI;
+                        if (r < s._radius && r > s._innerRadius) {
+                            for (j=0; j<s.gridData.length; j++) {
+                                minang = (j>0) ? s.gridData[j-1][1]+sm : sm;
+                                maxang = s.gridData[j][1];
+                                if (theta > minang && theta < maxang) {
+                                    return {seriesIndex:s.index, pointIndex:j, gridData:[gridpos.x,gridpos.y], data:s.data[j]};
+                                }
+                            }
+                        }
+                        break;
+                        
+                    case $.jqplot.PieRenderer:
+                        sa = s.startAngle/180*Math.PI;
+                        x = gridpos.x - s._center[0];
+                        y = gridpos.y - s._center[1];
+                        r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
+                        if (x > 0 && -y >= 0) {
+                            theta = 2*Math.PI - Math.atan(-y/x);
+                        }
+                        else if (x > 0 && -y < 0) {
+                            theta = -Math.atan(-y/x);
+                        }
+                        else if (x < 0) {
+                            theta = Math.PI - Math.atan(-y/x);
+                        }
+                        else if (x == 0 && -y > 0) {
+                            theta = 3*Math.PI/2;
+                        }
+                        else if (x == 0 && -y < 0) {
+                            theta = Math.PI/2;
+                        }
+                        else if (x == 0 && y == 0) {
+                            theta = 0;
+                        }
+                        if (sa) {
+                            theta -= sa;
+                            if (theta < 0) {
+                                theta += 2*Math.PI;
+                            }
+                            else if (theta > 2*Math.PI) {
+                                theta -= 2*Math.PI;
+                            }
+                        }
+            
+                        sm = s.sliceMargin/180*Math.PI;
+                        if (r < s._radius) {
+                            for (j=0; j<s.gridData.length; j++) {
+                                minang = (j>0) ? s.gridData[j-1][1]+sm : sm;
+                                maxang = s.gridData[j][1];
+                                if (theta > minang && theta < maxang) {
+                                    return {seriesIndex:s.index, pointIndex:j, gridData:[gridpos.x,gridpos.y], data:s.data[j]};
+                                }
+                            }
+                        }
+                        break;
+                        
+                    case $.jqplot.BubbleRenderer:
+                        x = gridpos.x;
+                        y = gridpos.y;
+                        var ret = null;
+                        
+                        if (s.show) {
+                            for (var j=0; j<s.gridData.length; j++) {
+                                p = s.gridData[j];
+                                d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
+                                if (d <= p[2] && (d <= d0 || d0 == null)) {
+                                   d0 = d;
+                                   ret = {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                }
+                            }
+                            if (ret != null) {
+                                return ret;
+                            }
+                        }
+                        break;
+                        
+                    case $.jqplot.FunnelRenderer:
+                        x = gridpos.x;
+                        y = gridpos.y;
+                        var v = s._vertices,
+                            vfirst = v[0],
+                            vlast = v[v.length-1],
+                            lex,
+                            rex,
+                            cv;
+    
+                        // equations of right and left sides, returns x, y values given height of section (y value and 2 points)
+    
+                        function findedge (l, p1 , p2) {
+                            var m = (p1[1] - p2[1])/(p1[0] - p2[0]);
+                            var b = p1[1] - m*p1[0];
+                            var y = l + p1[1];
+        
+                            return [(y - b)/m, y];
+                        }
+    
+                        // check each section
+                        lex = findedge(y, vfirst[0], vlast[3]);
+                        rex = findedge(y, vfirst[1], vlast[2]);
+                        for (j=0; j<v.length; j++) {
+                            cv = v[j];
+                            if (y >= cv[0][1] && y <= cv[3][1] && x >= lex[0] && x <= rex[0]) {
+                                return {seriesIndex:s.index, pointIndex:j, gridData:null, data:s.data[j]};
+                            }
+                        }         
+                        break;           
+                    
+                    case $.jqplot.LineRenderer:
+                        x = gridpos.x;
+                        y = gridpos.y;
+                        r = s.renderer;
+                        if (s.show) {
+                            if ((s.fill || (s.renderer.bands.show && s.renderer.bands.fill)) && (!plot.plugins.highlighter || !plot.plugins.highlighter.show)) {
+                                // first check if it is in bounding box
+                                var inside = false;
+                                if (x>s._boundingBox[0][0] && x<s._boundingBox[1][0] && y>s._boundingBox[1][1] && y<s._boundingBox[0][1]) { 
+                                    // now check the crossing number   
+                                    
+                                    var numPoints = s._areaPoints.length;
+                                    var ii;
+                                    var j = numPoints-1;
+
+                                    for(var ii=0; ii < numPoints; ii++) { 
+                                        var vertex1 = [s._areaPoints[ii][0], s._areaPoints[ii][1]];
+                                        var vertex2 = [s._areaPoints[j][0], s._areaPoints[j][1]];
+
+                                        if (vertex1[1] < y && vertex2[1] >= y || vertex2[1] < y && vertex1[1] >= y)     {
+                                            if (vertex1[0] + (y - vertex1[1]) / (vertex2[1] - vertex1[1]) * (vertex2[0] - vertex1[0]) < x) {
+                                                inside = !inside;
+                                            }
+                                        }
+
+                                        j = ii;
+                                    }        
+                                }
+                                if (inside) {
+                                    return {seriesIndex:i, pointIndex:null, gridData:s.gridData, data:s.data, points:s._areaPoints};
+                                }
+                                break;
+                                
+                            }
+
+                            else {
+                                t = s.markerRenderer.size/2+s.neighborThreshold;
+                                threshold = (t > 0) ? t : 0;
+                                for (var j=0; j<s.gridData.length; j++) {
+                                    p = s.gridData[j];
+                                    // neighbor looks different to OHLC chart.
+                                    if (r.constructor == $.jqplot.OHLCRenderer) {
+                                        if (r.candleStick) {
+                                            var yp = s._yaxis.series_u2p;
+                                            if (x >= p[0]-r._bodyWidth/2 && x <= p[0]+r._bodyWidth/2 && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
+                                                return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                            }
+                                        }
+                                        // if an open hi low close chart
+                                        else if (!r.hlc){
+                                            var yp = s._yaxis.series_u2p;
+                                            if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
+                                                return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                            }
+                                        }
+                                        // a hi low close chart
+                                        else {
+                                            var yp = s._yaxis.series_u2p;
+                                            if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) {
+                                                return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                            }
+                                        }
+                            
+                                    }
+                                    else if (p[0] != null && p[1] != null){
+                                        d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
+                                        if (d <= threshold && (d <= d0 || d0 == null)) {
+                                           d0 = d;
+                                           return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                        }
+                                    }
+                                } 
+                            }
+                        }
+                        break;
+                        
+                    default:
+                        x = gridpos.x;
+                        y = gridpos.y;
+                        r = s.renderer;
+                        if (s.show) {
+                            t = s.markerRenderer.size/2+s.neighborThreshold;
+                            threshold = (t > 0) ? t : 0;
+                            for (var j=0; j<s.gridData.length; j++) {
+                                p = s.gridData[j];
+                                // neighbor looks different to OHLC chart.
+                                if (r.constructor == $.jqplot.OHLCRenderer) {
+                                    if (r.candleStick) {
+                                        var yp = s._yaxis.series_u2p;
+                                        if (x >= p[0]-r._bodyWidth/2 && x <= p[0]+r._bodyWidth/2 && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
+                                            return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                        }
+                                    }
+                                    // if an open hi low close chart
+                                    else if (!r.hlc){
+                                        var yp = s._yaxis.series_u2p;
+                                        if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
+                                            return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                        }
+                                    }
+                                    // a hi low close chart
+                                    else {
+                                        var yp = s._yaxis.series_u2p;
+                                        if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) {
+                                            return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                        }
+                                    }
+                            
+                                }
+                                else {
+                                    d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
+                                    if (d <= threshold && (d <= d0 || d0 == null)) {
+                                       d0 = d;
+                                       return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
+                                    }
+                                }
+                            } 
+                        }
+                        break;
+                }
+            }
+            
+            return null;
+        }
+        
+        
+        
+        this.onClick = function(ev) {
+            // Event passed in is normalized and will have data attribute.
+            // Event passed out is unnormalized.
+            var positions = getEventPosition(ev);
+            var p = ev.data.plot;
+            var neighbor = checkIntersection(positions.gridPos, p);
+            var evt = $.Event('jqplotClick');
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
+        };
+        
+        this.onDblClick = function(ev) {
+            // Event passed in is normalized and will have data attribute.
+            // Event passed out is unnormalized.
+            var positions = getEventPosition(ev);
+            var p = ev.data.plot;
+            var neighbor = checkIntersection(positions.gridPos, p);
+            var evt = $.Event('jqplotDblClick');
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
+        };
+        
+        this.onMouseDown = function(ev) {
+            var positions = getEventPosition(ev);
+            var p = ev.data.plot;
+            var neighbor = checkIntersection(positions.gridPos, p);
+            var evt = $.Event('jqplotMouseDown');
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
+        };
+        
+        this.onMouseUp = function(ev) {
+            var positions = getEventPosition(ev);
+            var evt = $.Event('jqplotMouseUp');
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, ev.data.plot]);
+        };
+        
+        this.onRightClick = function(ev) {
+            var positions = getEventPosition(ev);
+            var p = ev.data.plot;
+            var neighbor = checkIntersection(positions.gridPos, p);
+            if (p.captureRightClick) {
+                if (ev.which == 3) {
+                var evt = $.Event('jqplotRightClick');
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                    $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
+                }
+                else {
+                var evt = $.Event('jqplotMouseUp');
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                    $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
+                }
+            }
+        };
+        
+        this.onMouseMove = function(ev) {
+            var positions = getEventPosition(ev);
+            var p = ev.data.plot;
+            var neighbor = checkIntersection(positions.gridPos, p);
+            var evt = $.Event('jqplotMouseMove');
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
+        };
+        
+        this.onMouseEnter = function(ev) {
+            var positions = getEventPosition(ev);
+            var p = ev.data.plot;
+            var evt = $.Event('jqplotMouseEnter');
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            evt.relatedTarget = ev.relatedTarget;
+            $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]);
+        };
+        
+        this.onMouseLeave = function(ev) {
+            var positions = getEventPosition(ev);
+            var p = ev.data.plot;
+            var evt = $.Event('jqplotMouseLeave');
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            evt.relatedTarget = ev.relatedTarget;
+            $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]);
+        };
+        
+        // method: drawSeries
+        // Redraws all or just one series on the plot.  No axis scaling
+        // is performed and no other elements on the plot are redrawn.
+        // options is an options object to pass on to the series renderers.
+        // It can be an empty object {}.  idx is the series index
+        // to redraw if only one series is to be redrawn.
+        this.drawSeries = function(options, idx){
+            var i, series, ctx;
+            // if only one argument passed in and it is a number, use it ad idx.
+            idx = (typeof(options) === "number" && idx == null) ? options : idx;
+            options = (typeof(options) === "object") ? options : {};
+            // draw specified series
+            if (idx != undefined) {
+                series = this.series[idx];
+                ctx = series.shadowCanvas._ctx;
+                ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+                series.drawShadow(ctx, options, this);
+                ctx = series.canvas._ctx;
+                ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+                series.draw(ctx, options, this);
+                if (series.renderer.constructor == $.jqplot.BezierCurveRenderer) {
+                    if (idx < this.series.length - 1) {
+                        this.drawSeries(idx+1); 
+                    }
+                }
+            }
+            
+            else {
+                // if call series drawShadow method first, in case all series shadows
+                // should be drawn before any series.  This will ensure, like for 
+                // stacked bar plots, that shadows don't overlap series.
+                for (i=0; i<this.series.length; i++) {
+                    // first clear the canvas
+                    series = this.series[i];
+                    ctx = series.shadowCanvas._ctx;
+                    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+                    series.drawShadow(ctx, options, this);
+                    ctx = series.canvas._ctx;
+                    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+                    series.draw(ctx, options, this);
+                }
+            }
+            options = idx = i = series = ctx = null;
+        };
+        
+        // method: moveSeriesToFront
+        // This method requires jQuery 1.4+
+        // Moves the specified series canvas in front of all other series canvases.
+        // This effectively "draws" the specified series on top of all other series,
+        // although it is performed through DOM manipulation, no redrawing is performed.
+        //
+        // Parameters:
+        // idx - 0 based index of the series to move.  This will be the index of the series
+        // as it was first passed into the jqplot function.
+        this.moveSeriesToFront = function (idx) { 
+            idx = parseInt(idx, 10);
+            var stackIndex = $.inArray(idx, this.seriesStack);
+            // if already in front, return
+            if (stackIndex == -1) {
+                return;
+            }
+            if (stackIndex == this.seriesStack.length -1) {
+                this.previousSeriesStack = this.seriesStack.slice(0);
+                return;
+            }
+            var opidx = this.seriesStack[this.seriesStack.length -1];
+            var serelem = this.series[idx].canvas._elem.detach();
+            var shadelem = this.series[idx].shadowCanvas._elem.detach();
+            this.series[opidx].shadowCanvas._elem.after(shadelem);
+            this.series[opidx].canvas._elem.after(serelem);
+            this.previousSeriesStack = this.seriesStack.slice(0);
+            this.seriesStack.splice(stackIndex, 1);
+            this.seriesStack.push(idx);
+        };
+        
+        // method: moveSeriesToBack
+        // This method requires jQuery 1.4+
+        // Moves the specified series canvas behind all other series canvases.
+        //
+        // Parameters:
+        // idx - 0 based index of the series to move.  This will be the index of the series
+        // as it was first passed into the jqplot function.
+        this.moveSeriesToBack = function (idx) {
+            idx = parseInt(idx, 10);
+            var stackIndex = $.inArray(idx, this.seriesStack);
+            // if already in back, return
+            if (stackIndex == 0 || stackIndex == -1) {
+                return;
+            }
+            var opidx = this.seriesStack[0];
+            var serelem = this.series[idx].canvas._elem.detach();
+            var shadelem = this.series[idx].shadowCanvas._elem.detach();
+            this.series[opidx].shadowCanvas._elem.before(shadelem);
+            this.series[opidx].canvas._elem.before(serelem);
+            this.previousSeriesStack = this.seriesStack.slice(0);
+            this.seriesStack.splice(stackIndex, 1);
+            this.seriesStack.unshift(idx);
+        };
+        
+        // method: restorePreviousSeriesOrder
+        // This method requires jQuery 1.4+
+        // Restore the series canvas order to its previous state.
+        // Useful to put a series back where it belongs after moving
+        // it to the front.
+        this.restorePreviousSeriesOrder = function () {
+            var i, j, serelem, shadelem, temp, move, keep;
+            // if no change, return.
+            if (this.seriesStack == this.previousSeriesStack) {
+                return;
+            }
+            for (i=1; i<this.previousSeriesStack.length; i++) {
+                move = this.previousSeriesStack[i];
+                keep = this.previousSeriesStack[i-1];
+                serelem = this.series[move].canvas._elem.detach();
+                shadelem = this.series[move].shadowCanvas._elem.detach();
+                this.series[keep].shadowCanvas._elem.after(shadelem);
+                this.series[keep].canvas._elem.after(serelem);
+            }
+            temp = this.seriesStack.slice(0);
+            this.seriesStack = this.previousSeriesStack.slice(0);
+            this.previousSeriesStack = temp;
+        };
+        
+        // method: restoreOriginalSeriesOrder
+        // This method requires jQuery 1.4+
+        // Restore the series canvas order to its original order
+        // when the plot was created.
+        this.restoreOriginalSeriesOrder = function () {
+            var i, j, arr=[], serelem, shadelem;
+            for (i=0; i<this.series.length; i++) {
+                arr.push(i);
+            }
+            if (this.seriesStack == arr) {
+                return;
+            }
+            this.previousSeriesStack = this.seriesStack.slice(0);
+            this.seriesStack = arr;
+            for (i=1; i<this.seriesStack.length; i++) {
+                serelem = this.series[i].canvas._elem.detach();
+                shadelem = this.series[i].shadowCanvas._elem.detach();
+                this.series[i-1].shadowCanvas._elem.after(shadelem);
+                this.series[i-1].canvas._elem.after(serelem);
+            }
+        };
+        
+        this.activateTheme = function (name) {
+            this.themeEngine.activate(this, name);
+        };
+    }
+    
+    
+    // conpute a highlight color or array of highlight colors from given colors.
+    $.jqplot.computeHighlightColors  = function(colors) {
+        var ret;
+        if ($.isArray(colors)) {
+            ret = [];
+            for (var i=0; i<colors.length; i++){
+                var rgba = $.jqplot.getColorComponents(colors[i]);
+                var newrgb = [rgba[0], rgba[1], rgba[2]];
+                var sum = newrgb[0] + newrgb[1] + newrgb[2];
+                for (var j=0; j<3; j++) {
+                    // when darkening, lowest color component can be is 60.
+                    newrgb[j] = (sum > 660) ?  newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90;
+                    newrgb[j] = parseInt(newrgb[j], 10);
+                    (newrgb[j] > 255) ? 255 : newrgb[j];
+                }
+                // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5;
+                // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2;
+                newrgb[3] = 0.3 + 0.35 * rgba[3];
+                ret.push('rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')');
+            }
+        }
+        else {
+            var rgba = $.jqplot.getColorComponents(colors);
+            var newrgb = [rgba[0], rgba[1], rgba[2]];
+            var sum = newrgb[0] + newrgb[1] + newrgb[2];
+            for (var j=0; j<3; j++) {
+                // when darkening, lowest color component can be is 60.
+                // newrgb[j] = (sum > 570) ?  newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
+                // newrgb[j] = parseInt(newrgb[j], 10);
+                newrgb[j] = (sum > 660) ?  newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90;
+                newrgb[j] = parseInt(newrgb[j], 10);
+                (newrgb[j] > 255) ? 255 : newrgb[j];
+            }
+            // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5;
+            // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2;
+            newrgb[3] = 0.3 + 0.35 * rgba[3];
+            ret = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')';
+        }
+        return ret;
+    };
+        
+   $.jqplot.ColorGenerator = function(colors) {
+        colors = colors || $.jqplot.config.defaultColors;
+        var idx = 0;
+        
+        this.next = function () { 
+            if (idx < colors.length) {
+                return colors[idx++];
+            }
+            else {
+                idx = 0;
+                return colors[idx++];
+            }
+        };
+        
+        this.previous = function () { 
+            if (idx > 0) {
+                return colors[idx--];
+            }
+            else {
+                idx = colors.length-1;
+                return colors[idx];
+            }
+        };
+        
+        // get a color by index without advancing pointer.
+        this.get = function(i) {
+            var idx = i - colors.length * Math.floor(i/colors.length);
+            return colors[idx];
+        };
+        
+        this.setColors = function(c) {
+            colors = c;
+        };
+        
+        this.reset = function() {
+            idx = 0;
+        };
+
+        this.getIndex = function() {
+            return idx;
+        };
+
+        this.setIndex = function(index) {
+            idx = index;
+        };
+    };
+
+    // convert a hex color string to rgb string.
+    // h - 3 or 6 character hex string, with or without leading #
+    // a - optional alpha
+    $.jqplot.hex2rgb = function(h, a) {
+        h = h.replace('#', '');
+        if (h.length == 3) {
+            h = h.charAt(0)+h.charAt(0)+h.charAt(1)+h.charAt(1)+h.charAt(2)+h.charAt(2);
+        }
+        var rgb;
+        rgb = 'rgba('+parseInt(h.slice(0,2), 16)+', '+parseInt(h.slice(2,4), 16)+', '+parseInt(h.slice(4,6), 16);
+        if (a) {
+            rgb += ', '+a;
+        }
+        rgb += ')';
+        return rgb;
+    };
+    
+    // convert an rgb color spec to a hex spec.  ignore any alpha specification.
+    $.jqplot.rgb2hex = function(s) {
+        var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *(?:, *[0-9.]*)?\)/;
+        var m = s.match(pat);
+        var h = '#';
+        for (var i=1; i<4; i++) {
+            var temp;
+            if (m[i].search(/%/) != -1) {
+                temp = parseInt(255*m[i]/100, 10).toString(16);
+                if (temp.length == 1) {
+                    temp = '0'+temp;
+                }
+            }
+            else {
+                temp = parseInt(m[i], 10).toString(16);
+                if (temp.length == 1) {
+                    temp = '0'+temp;
+                }
+            }
+            h += temp;
+        }
+        return h;
+    };
+    
+    // given a css color spec, return an rgb css color spec
+    $.jqplot.normalize2rgb = function(s, a) {
+        if (s.search(/^ *rgba?\(/) != -1) {
+            return s; 
+        }
+        else if (s.search(/^ *#?[0-9a-fA-F]?[0-9a-fA-F]/) != -1) {
+            return $.jqplot.hex2rgb(s, a);
+        }
+        else {
+            throw new Error('Invalid color spec');
+        }
+    };
+    
+    // extract the r, g, b, a color components out of a css color spec.
+    $.jqplot.getColorComponents = function(s) {
+        // check to see if a color keyword.
+        s = $.jqplot.colorKeywordMap[s] || s;
+        var rgb = $.jqplot.normalize2rgb(s);
+        var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *,? *([0-9.]* *)?\)/;
+        var m = rgb.match(pat);
+        var ret = [];
+        for (var i=1; i<4; i++) {
+            if (m[i].search(/%/) != -1) {
+                ret[i-1] = parseInt(255*m[i]/100, 10);
+            }
+            else {
+                ret[i-1] = parseInt(m[i], 10);
+            }
+        }
+        ret[3] = parseFloat(m[4]) ? parseFloat(m[4]) : 1.0;
+        return ret;
+    };
+    
+    $.jqplot.colorKeywordMap = {
+        aliceblue: 'rgb(240, 248, 255)',
+        antiquewhite: 'rgb(250, 235, 215)',
+        aqua: 'rgb( 0, 255, 255)',
+        aquamarine: 'rgb(127, 255, 212)',
+        azure: 'rgb(240, 255, 255)',
+        beige: 'rgb(245, 245, 220)',
+        bisque: 'rgb(255, 228, 196)',
+        black: 'rgb( 0, 0, 0)',
+        blanchedalmond: 'rgb(255, 235, 205)',
+        blue: 'rgb( 0, 0, 255)',
+        blueviolet: 'rgb(138, 43, 226)',
+        brown: 'rgb(165, 42, 42)',
+        burlywood: 'rgb(222, 184, 135)',
+        cadetblue: 'rgb( 95, 158, 160)',
+        chartreuse: 'rgb(127, 255, 0)',
+        chocolate: 'rgb(210, 105, 30)',
+        coral: 'rgb(255, 127, 80)',
+        cornflowerblue: 'rgb(100, 149, 237)',
+        cornsilk: 'rgb(255, 248, 220)',
+        crimson: 'rgb(220, 20, 60)',
+        cyan: 'rgb( 0, 255, 255)',
+        darkblue: 'rgb( 0, 0, 139)',
+        darkcyan: 'rgb( 0, 139, 139)',
+        darkgoldenrod: 'rgb(184, 134, 11)',
+        darkgray: 'rgb(169, 169, 169)',
+        darkgreen: 'rgb( 0, 100, 0)',
+        darkgrey: 'rgb(169, 169, 169)',
+        darkkhaki: 'rgb(189, 183, 107)',
+        darkmagenta: 'rgb(139, 0, 139)',
+        darkolivegreen: 'rgb( 85, 107, 47)',
+        darkorange: 'rgb(255, 140, 0)',
+        darkorchid: 'rgb(153, 50, 204)',
+        darkred: 'rgb(139, 0, 0)',
+        darksalmon: 'rgb(233, 150, 122)',
+        darkseagreen: 'rgb(143, 188, 143)',
+        darkslateblue: 'rgb( 72, 61, 139)',
+        darkslategray: 'rgb( 47, 79, 79)',
+        darkslategrey: 'rgb( 47, 79, 79)',
+        darkturquoise: 'rgb( 0, 206, 209)',
+        darkviolet: 'rgb(148, 0, 211)',
+        deeppink: 'rgb(255, 20, 147)',
+        deepskyblue: 'rgb( 0, 191, 255)',
+        dimgray: 'rgb(105, 105, 105)',
+        dimgrey: 'rgb(105, 105, 105)',
+        dodgerblue: 'rgb( 30, 144, 255)',
+        firebrick: 'rgb(178, 34, 34)',
+        floralwhite: 'rgb(255, 250, 240)',
+        forestgreen: 'rgb( 34, 139, 34)',
+        fuchsia: 'rgb(255, 0, 255)',
+        gainsboro: 'rgb(220, 220, 220)',
+        ghostwhite: 'rgb(248, 248, 255)',
+        gold: 'rgb(255, 215, 0)',
+        goldenrod: 'rgb(218, 165, 32)',
+        gray: 'rgb(128, 128, 128)',
+        grey: 'rgb(128, 128, 128)',
+        green: 'rgb( 0, 128, 0)',
+        greenyellow: 'rgb(173, 255, 47)',
+        honeydew: 'rgb(240, 255, 240)',
+        hotpink: 'rgb(255, 105, 180)',
+        indianred: 'rgb(205, 92, 92)',
+        indigo: 'rgb( 75, 0, 130)',
+        ivory: 'rgb(255, 255, 240)',
+        khaki: 'rgb(240, 230, 140)',
+        lavender: 'rgb(230, 230, 250)',
+        lavenderblush: 'rgb(255, 240, 245)',
+        lawngreen: 'rgb(124, 252, 0)',
+        lemonchiffon: 'rgb(255, 250, 205)',
+        lightblue: 'rgb(173, 216, 230)',
+        lightcoral: 'rgb(240, 128, 128)',
+        lightcyan: 'rgb(224, 255, 255)',
+        lightgoldenrodyellow: 'rgb(250, 250, 210)',
+        lightgray: 'rgb(211, 211, 211)',
+        lightgreen: 'rgb(144, 238, 144)',
+        lightgrey: 'rgb(211, 211, 211)',
+        lightpink: 'rgb(255, 182, 193)',
+        lightsalmon: 'rgb(255, 160, 122)',
+        lightseagreen: 'rgb( 32, 178, 170)',
+        lightskyblue: 'rgb(135, 206, 250)',
+        lightslategray: 'rgb(119, 136, 153)',
+        lightslategrey: 'rgb(119, 136, 153)',
+        lightsteelblue: 'rgb(176, 196, 222)',
+        lightyellow: 'rgb(255, 255, 224)',
+        lime: 'rgb( 0, 255, 0)',
+        limegreen: 'rgb( 50, 205, 50)',
+        linen: 'rgb(250, 240, 230)',
+        magenta: 'rgb(255, 0, 255)',
+        maroon: 'rgb(128, 0, 0)',
+        mediumaquamarine: 'rgb(102, 205, 170)',
+        mediumblue: 'rgb( 0, 0, 205)',
+        mediumorchid: 'rgb(186, 85, 211)',
+        mediumpurple: 'rgb(147, 112, 219)',
+        mediumseagreen: 'rgb( 60, 179, 113)',
+        mediumslateblue: 'rgb(123, 104, 238)',
+        mediumspringgreen: 'rgb( 0, 250, 154)',
+        mediumturquoise: 'rgb( 72, 209, 204)',
+        mediumvioletred: 'rgb(199, 21, 133)',
+        midnightblue: 'rgb( 25, 25, 112)',
+        mintcream: 'rgb(245, 255, 250)',
+        mistyrose: 'rgb(255, 228, 225)',
+        moccasin: 'rgb(255, 228, 181)',
+        navajowhite: 'rgb(255, 222, 173)',
+        navy: 'rgb( 0, 0, 128)',
+        oldlace: 'rgb(253, 245, 230)',
+        olive: 'rgb(128, 128, 0)',
+        olivedrab: 'rgb(107, 142, 35)',
+        orange: 'rgb(255, 165, 0)',
+        orangered: 'rgb(255, 69, 0)',
+        orchid: 'rgb(218, 112, 214)',
+        palegoldenrod: 'rgb(238, 232, 170)',
+        palegreen: 'rgb(152, 251, 152)',
+        paleturquoise: 'rgb(175, 238, 238)',
+        palevioletred: 'rgb(219, 112, 147)',
+        papayawhip: 'rgb(255, 239, 213)',
+        peachpuff: 'rgb(255, 218, 185)',
+        peru: 'rgb(205, 133, 63)',
+        pink: 'rgb(255, 192, 203)',
+        plum: 'rgb(221, 160, 221)',
+        powderblue: 'rgb(176, 224, 230)',
+        purple: 'rgb(128, 0, 128)',
+        red: 'rgb(255, 0, 0)',
+        rosybrown: 'rgb(188, 143, 143)',
+        royalblue: 'rgb( 65, 105, 225)',
+        saddlebrown: 'rgb(139, 69, 19)',
+        salmon: 'rgb(250, 128, 114)',
+        sandybrown: 'rgb(244, 164, 96)',
+        seagreen: 'rgb( 46, 139, 87)',
+        seashell: 'rgb(255, 245, 238)',
+        sienna: 'rgb(160, 82, 45)',
+        silver: 'rgb(192, 192, 192)',
+        skyblue: 'rgb(135, 206, 235)',
+        slateblue: 'rgb(106, 90, 205)',
+        slategray: 'rgb(112, 128, 144)',
+        slategrey: 'rgb(112, 128, 144)',
+        snow: 'rgb(255, 250, 250)',
+        springgreen: 'rgb( 0, 255, 127)',
+        steelblue: 'rgb( 70, 130, 180)',
+        tan: 'rgb(210, 180, 140)',
+        teal: 'rgb( 0, 128, 128)',
+        thistle: 'rgb(216, 191, 216)',
+        tomato: 'rgb(255, 99, 71)',
+        turquoise: 'rgb( 64, 224, 208)',
+        violet: 'rgb(238, 130, 238)',
+        wheat: 'rgb(245, 222, 179)',
+        white: 'rgb(255, 255, 255)',
+        whitesmoke: 'rgb(245, 245, 245)',
+        yellow: 'rgb(255, 255, 0)',
+        yellowgreen: 'rgb(154, 205, 50)'
+    };
+
+    
+
+
+    // class: $.jqplot.AxisLabelRenderer
+    // Renderer to place labels on the axes.
+    $.jqplot.AxisLabelRenderer = function(options) {
+        // Group: Properties
+        $.jqplot.ElemContainer.call(this);
+        // name of the axis associated with this tick
+        this.axis;
+        // prop: show
+        // whether or not to show the tick (mark and label).
+        this.show = true;
+        // prop: label
+        // The text or html for the label.
+        this.label = '';
+        this.fontFamily = null;
+        this.fontSize = null;
+        this.textColor = null;
+        this._elem;
+        // prop: escapeHTML
+        // true to escape HTML entities in the label.
+        this.escapeHTML = false;
+        
+        $.extend(true, this, options);
+    };
+    
+    $.jqplot.AxisLabelRenderer.prototype = new $.jqplot.ElemContainer();
+    $.jqplot.AxisLabelRenderer.prototype.constructor = $.jqplot.AxisLabelRenderer;
+    
+    $.jqplot.AxisLabelRenderer.prototype.init = function(options) {
+        $.extend(true, this, options);
+    };
+    
+    $.jqplot.AxisLabelRenderer.prototype.draw = function(ctx, plot) {
+        // Memory Leaks patch
+        if (this._elem) {
+            this._elem.emptyForce();
+            this._elem = null;
+        }
+
+        this._elem = $('<div style="position:absolute;" class="jqplot-'+this.axis+'-label"></div>');
+        
+        if (Number(this.label)) {
+            this._elem.css('white-space', 'nowrap');
+        }
+        
+        if (!this.escapeHTML) {
+            this._elem.html(this.label);
+        }
+        else {
+            this._elem.text(this.label);
+        }
+        if (this.fontFamily) {
+            this._elem.css('font-family', this.fontFamily);
+        }
+        if (this.fontSize) {
+            this._elem.css('font-size', this.fontSize);
+        }
+        if (this.textColor) {
+            this._elem.css('color', this.textColor);
+        }
+        
+        return this._elem;
+    };
+    
+    $.jqplot.AxisLabelRenderer.prototype.pack = function() {
+    };
+
+    // class: $.jqplot.AxisTickRenderer
+    // A "tick" object showing the value of a tick/gridline on the plot.
+    $.jqplot.AxisTickRenderer = function(options) {
+        // Group: Properties
+        $.jqplot.ElemContainer.call(this);
+        // prop: mark
+        // tick mark on the axis.  One of 'inside', 'outside', 'cross', '' or null.
+        this.mark = 'outside';
+        // name of the axis associated with this tick
+        this.axis;
+        // prop: showMark
+        // whether or not to show the mark on the axis.
+        this.showMark = true;
+        // prop: showGridline
+        // whether or not to draw the gridline on the grid at this tick.
+        this.showGridline = true;
+        // prop: isMinorTick
+        // if this is a minor tick.
+        this.isMinorTick = false;
+        // prop: size
+        // Length of the tick beyond the grid in pixels.
+        // DEPRECATED: This has been superceeded by markSize
+        this.size = 4;
+        // prop:  markSize
+        // Length of the tick marks in pixels.  For 'cross' style, length
+        // will be stoked above and below axis, so total length will be twice this.
+        this.markSize = 6;
+        // prop: show
+        // whether or not to show the tick (mark and label).
+        // Setting this to false requires more testing.  It is recommended
+        // to set showLabel and showMark to false instead.
+        this.show = true;
+        // prop: showLabel
+        // whether or not to show the label.
+        this.showLabel = true;
+        this.label = null;
+        this.value = null;
+        this._styles = {};
+        // prop: formatter
+        // A class of a formatter for the tick text.  sprintf by default.
+        this.formatter = $.jqplot.DefaultTickFormatter;
+        // prop: prefix
+        // String to prepend to the tick label.
+        // Prefix is prepended to the formatted tick label.
+        this.prefix = '';
+        // prop: suffix
+        // String to append to the tick label.
+        // Suffix is appended to the formatted tick label.
+        this.suffix = '';
+        // prop: formatString
+        // string passed to the formatter.
+        this.formatString = '';
+        // prop: fontFamily
+        // css spec for the font-family css attribute.
+        this.fontFamily;
+        // prop: fontSize
+        // css spec for the font-size css attribute.
+        this.fontSize;
+        // prop: textColor
+        // css spec for the color attribute.
+        this.textColor;
+        // prop: escapeHTML
+        // true to escape HTML entities in the label.
+        this.escapeHTML = false;
+        this._elem;
+        this._breakTick = false;
+        
+        $.extend(true, this, options);
+    };
+    
+    $.jqplot.AxisTickRenderer.prototype.init = function(options) {
+        $.extend(true, this, options);
+    };
+    
+    $.jqplot.AxisTickRenderer.prototype = new $.jqplot.ElemContainer();
+    $.jqplot.AxisTickRenderer.prototype.constructor = $.jqplot.AxisTickRenderer;
+    
+    $.jqplot.AxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) {
+        this.value = value;
+        this.axis = axisName;
+        if (isMinor) {
+            this.isMinorTick = true;
+        }
+        return this;
+    };
+    
+    $.jqplot.AxisTickRenderer.prototype.draw = function() {
+        if (this.label === null) {
+            this.label = this.prefix + this.formatter(this.formatString, this.value) + this.suffix;
+        }
+        var style = {position: 'absolute'};
+        if (Number(this.label)) {
+            style['whitSpace'] = 'nowrap';
+        }
+        
+        // Memory Leaks patch
+        if (this._elem) {
+            this._elem.emptyForce();
+            this._elem = null;
+        }
+
+        this._elem = $(document.createElement('div'));
+        this._elem.addClass("jqplot-"+this.axis+"-tick");
+        
+        if (!this.escapeHTML) {
+            this._elem.html(this.label);
+        }
+        else {
+            this._elem.text(this.label);
+        }
+        
+        this._elem.css(style);
+
+        for (var s in this._styles) {
+            this._elem.css(s, this._styles[s]);
+        }
+        if (this.fontFamily) {
+            this._elem.css('font-family', this.fontFamily);
+        }
+        if (this.fontSize) {
+            this._elem.css('font-size', this.fontSize);
+        }
+        if (this.textColor) {
+            this._elem.css('color', this.textColor);
+        }
+        if (this._breakTick) {
+          this._elem.addClass('jqplot-breakTick');
+        }
+        
+        return this._elem;
+    };
+        
+    $.jqplot.DefaultTickFormatter = function (format, val) {
+        if (typeof val == 'number') {
+            if (!format) {
+                format = $.jqplot.config.defaultTickFormatString;
+            }
+            return $.jqplot.sprintf(format, val);
+        }
+        else {
+            return String(val);
+        }
+    };
+        
+    $.jqplot.PercentTickFormatter = function (format, val) {
+        if (typeof val == 'number') {
+            val = 100 * val;
+            if (!format) {
+                format = $.jqplot.config.defaultTickFormatString;
+            }
+            return $.jqplot.sprintf(format, val);
+        }
+        else {
+            return String(val);
+        }
+    };
+    
+    $.jqplot.AxisTickRenderer.prototype.pack = function() {
+    };
+     
+    // Class: $.jqplot.CanvasGridRenderer
+    // The default jqPlot grid renderer, creating a grid on a canvas element.
+    // The renderer has no additional options beyond the <Grid> class.
+    $.jqplot.CanvasGridRenderer = function(){
+        this.shadowRenderer = new $.jqplot.ShadowRenderer();
+    };
+    
+    // called with context of Grid object
+    $.jqplot.CanvasGridRenderer.prototype.init = function(options) {
+        this._ctx;
+        $.extend(true, this, options);
+        // set the shadow renderer options
+        var sopts = {lineJoin:'miter', lineCap:'round', fill:false, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.shadowWidth, closePath:false, strokeStyle:this.shadowColor};
+        this.renderer.shadowRenderer.init(sopts);
+    };
+    
+    // called with context of Grid.
+    $.jqplot.CanvasGridRenderer.prototype.createElement = function(plot) {
+        var elem;
+        // Memory Leaks patch
+        if (this._elem) {
+          if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+            elem = this._elem.get(0);
+            window.G_vmlCanvasManager.uninitElement(elem);
+            elem = null;
+          }
+          
+          this._elem.emptyForce();
+          this._elem = null;
+        }
+      
+        elem = plot.canvasManager.getCanvas();
+
+        var w = this._plotDimensions.width;
+        var h = this._plotDimensions.height;
+        elem.width = w;
+        elem.height = h;
+        this._elem = $(elem);
+        this._elem.addClass('jqplot-grid-canvas');
+        this._elem.css({ position: 'absolute', left: 0, top: 0 });
+        
+        elem = plot.canvasManager.initCanvas(elem);
+
+        this._top = this._offsets.top;
+        this._bottom = h - this._offsets.bottom;
+        this._left = this._offsets.left;
+        this._right = w - this._offsets.right;
+        this._width = this._right - this._left;
+        this._height = this._bottom - this._top;
+        // avoid memory leak
+        elem = null;
+        return this._elem;
+    };
+    
+    $.jqplot.CanvasGridRenderer.prototype.draw = function() {
+        this._ctx = this._elem.get(0).getContext("2d");
+        var ctx = this._ctx;
+        var axes = this._axes;
+        // Add the grid onto the grid canvas.  This is the bottom most layer.
+        ctx.save();
+        ctx.clearRect(0, 0, this._plotDimensions.width, this._plotDimensions.height);
+        ctx.fillStyle = this.backgroundColor || this.background;
+        ctx.fillRect(this._left, this._top, this._width, this._height);
+        
+        ctx.save();
+        ctx.lineJoin = 'miter';
+        ctx.lineCap = 'butt';
+        ctx.lineWidth = this.gridLineWidth;
+        ctx.strokeStyle = this.gridLineColor;
+        var b, e, s, m;
+        var ax = ['xaxis', 'yaxis', 'x2axis', 'y2axis'];
+        for (var i=4; i>0; i--) {
+            var name = ax[i-1];
+            var axis = axes[name];
+            var ticks = axis._ticks;
+            var numticks = ticks.length;
+            if (axis.show) {
+                if (axis.drawBaseline) {
+                    var bopts = {};
+                    if (axis.baselineWidth !== null) {
+                        bopts.lineWidth = axis.baselineWidth;
+                    }
+                    if (axis.baselineColor !== null) {
+                        bopts.strokeStyle = axis.baselineColor;
+                    }
+                    switch (name) {
+                        case 'xaxis':
+                            drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
+                            break;
+                        case 'yaxis':
+                            drawLine (this._left, this._bottom, this._left, this._top, bopts);
+                            break;
+                        case 'x2axis':
+                            drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
+                            break;
+                        case 'y2axis':
+                            drawLine (this._right, this._bottom, this._right, this._top, bopts);
+                            break;
+                    }
+                }
+                for (var j=numticks; j>0; j--) {
+                    var t = ticks[j-1];
+                    if (t.show) {
+                        var pos = Math.round(axis.u2p(t.value)) + 0.5;
+                        switch (name) {
+                            case 'xaxis':
+                                // draw the grid line if we should
+                                if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
+                                    drawLine(pos, this._top, pos, this._bottom);
+                                }
+                                // draw the mark
+                                if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
+                                    s = t.markSize;
+                                    m = t.mark;
+                                    var pos = Math.round(axis.u2p(t.value)) + 0.5;
+                                    switch (m) {
+                                        case 'outside':
+                                            b = this._bottom;
+                                            e = this._bottom+s;
+                                            break;
+                                        case 'inside':
+                                            b = this._bottom-s;
+                                            e = this._bottom;
+                                            break;
+                                        case 'cross':
+                                            b = this._bottom-s;
+                                            e = this._bottom+s;
+                                            break;
+                                        default:
+                                            b = this._bottom;
+                                            e = this._bottom+s;
+                                            break;
+                                    }
+                                    // draw the shadow
+                                    if (this.shadow) {
+                                        this.renderer.shadowRenderer.draw(ctx, [[pos,b],[pos,e]], {lineCap:'butt', lineWidth:this.gridLineWidth, offset:this.gridLineWidth*0.75, depth:2, fill:false, closePath:false});
+                                    }
+                                    // draw the line
+                                    drawLine(pos, b, pos, e);
+                                }
+                                break;
+                            case 'yaxis':
+                                // draw the grid line
+                                if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
+                                    drawLine(this._right, pos, this._left, pos);
+                                }
+                                // draw the mark
+                                if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
+                                    s = t.markSize;
+                                    m = t.mark;
+                                    var pos = Math.round(axis.u2p(t.value)) + 0.5;
+                                    switch (m) {
+                                        case 'outside':
+                                            b = this._left-s;
+                                            e = this._left;
+                                            break;
+                                        case 'inside':
+                                            b = this._left;
+                                            e = this._left+s;
+                                            break;
+                                        case 'cross':
+                                            b = this._left-s;
+                                            e = this._left+s;
+                                            break;
+                                        default:
+                                            b = this._left-s;
+                                            e = this._left;
+                                            break;
+                                            }
+                                    // draw the shadow
+                                    if (this.shadow) {
+                                        this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
+                                    }
+                                    drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
+                                }
+                                break;
+                            case 'x2axis':
+                                // draw the grid line
+                                if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
+                                    drawLine(pos, this._bottom, pos, this._top);
+                                }
+                                // draw the mark
+                                if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
+                                    s = t.markSize;
+                                    m = t.mark;
+                                    var pos = Math.round(axis.u2p(t.value)) + 0.5;
+                                    switch (m) {
+                                        case 'outside':
+                                            b = this._top-s;
+                                            e = this._top;
+                                            break;
+                                        case 'inside':
+                                            b = this._top;
+                                            e = this._top+s;
+                                            break;
+                                        case 'cross':
+                                            b = this._top-s;
+                                            e = this._top+s;
+                                            break;
+                                        default:
+                                            b = this._top-s;
+                                            e = this._top;
+                                            break;
+                                            }
+                                    // draw the shadow
+                                    if (this.shadow) {
+                                        this.renderer.shadowRenderer.draw(ctx, [[pos,b],[pos,e]], {lineCap:'butt', lineWidth:this.gridLineWidth, offset:this.gridLineWidth*0.75, depth:2, fill:false, closePath:false});
+                                    }
+                                    drawLine(pos, b, pos, e);
+                                }
+                                break;
+                            case 'y2axis':
+                                // draw the grid line
+                                if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
+                                    drawLine(this._left, pos, this._right, pos);
+                                }
+                                // draw the mark
+                                if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
+                                    s = t.markSize;
+                                    m = t.mark;
+                                    var pos = Math.round(axis.u2p(t.value)) + 0.5;
+                                    switch (m) {
+                                        case 'outside':
+                                            b = this._right;
+                                            e = this._right+s;
+                                            break;
+                                        case 'inside':
+                                            b = this._right-s;
+                                            e = this._right;
+                                            break;
+                                        case 'cross':
+                                            b = this._right-s;
+                                            e = this._right+s;
+                                            break;
+                                        default:
+                                            b = this._right;
+                                            e = this._right+s;
+                                            break;
+                                            }
+                                    // draw the shadow
+                                    if (this.shadow) {
+                                        this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
+                                    }
+                                    drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
+                                }
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                }
+                t = null;
+            }
+            axis = null;
+            ticks = null;
+        }
+        // Now draw grid lines for additional y axes
+        //////
+        // TO DO: handle yMidAxis
+        //////
+        ax = ['y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
+        for (var i=7; i>0; i--) {
+            var axis = axes[ax[i-1]];
+            var ticks = axis._ticks;
+            if (axis.show) {
+                var tn = ticks[axis.numberTicks-1];
+                var t0 = ticks[0];
+                var left = axis.getLeft();
+                var points = [[left, tn.getTop() + tn.getHeight()/2], [left, t0.getTop() + t0.getHeight()/2 + 1.0]];
+                // draw the shadow
+                if (this.shadow) {
+                    this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', fill:false, closePath:false});
+                }
+                // draw the line
+                drawLine(points[0][0], points[0][1], points[1][0], points[1][1], {lineCap:'butt', strokeStyle:axis.borderColor, lineWidth:axis.borderWidth});
+                // draw the tick marks
+                for (var j=ticks.length; j>0; j--) {
+                    var t = ticks[j-1];
+                    s = t.markSize;
+                    m = t.mark;
+                    var pos = Math.round(axis.u2p(t.value)) + 0.5;
+                    if (t.showMark && t.mark) {
+                        switch (m) {
+                            case 'outside':
+                                b = left;
+                                e = left+s;
+                                break;
+                            case 'inside':
+                                b = left-s;
+                                e = left;
+                                break;
+                            case 'cross':
+                                b = left-s;
+                                e = left+s;
+                                break;
+                            default:
+                                b = left;
+                                e = left+s;
+                                break;
+                        }
+                        points = [[b,pos], [e,pos]];
+                        // draw the shadow
+                        if (this.shadow) {
+                            this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
+                        }
+                        // draw the line
+                        drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
+                    }
+                    t = null;
+                }
+                t0 = null;
+            }
+            axis = null;
+            ticks =  null;
+        }
+        
+        ctx.restore();
+        
+        function drawLine(bx, by, ex, ey, opts) {
+            ctx.save();
+            opts = opts || {};
+            if (opts.lineWidth == null || opts.lineWidth != 0){
+                $.extend(true, ctx, opts);
+                ctx.beginPath();
+                ctx.moveTo(bx, by);
+                ctx.lineTo(ex, ey);
+                ctx.stroke();
+                ctx.restore();
+            }
+        }
+        
+        if (this.shadow) {
+            var points = [[this._left, this._bottom], [this._right, this._bottom], [this._right, this._top]];
+            this.renderer.shadowRenderer.draw(ctx, points);
+        }
+        // Now draw border around grid.  Use axis border definitions. start at
+        // upper left and go clockwise.
+        if (this.borderWidth != 0 && this.drawBorder) {
+            drawLine (this._left, this._top, this._right, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth});
+            drawLine (this._right, this._top, this._right, this._bottom, {lineCap:'round', strokeStyle:axes.y2axis.borderColor, lineWidth:axes.y2axis.borderWidth});
+            drawLine (this._right, this._bottom, this._left, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth});
+            drawLine (this._left, this._bottom, this._left, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
+        }
+        // ctx.lineWidth = this.borderWidth;
+        // ctx.strokeStyle = this.borderColor;
+        // ctx.strokeRect(this._left, this._top, this._width, this._height);
+        
+        ctx.restore();
+        ctx =  null;
+        axes = null;
+    };
+ 
+    // Class: $.jqplot.DivTitleRenderer
+    // The default title renderer for jqPlot.  This class has no options beyond the <Title> class. 
+    $.jqplot.DivTitleRenderer = function() {
+    };
+    
+    $.jqplot.DivTitleRenderer.prototype.init = function(options) {
+        $.extend(true, this, options);
+    };
+    
+    $.jqplot.DivTitleRenderer.prototype.draw = function() {
+        // Memory Leaks patch
+        if (this._elem) {
+            this._elem.emptyForce();
+            this._elem = null;
+        }
+
+        var r = this.renderer;
+        var elem = document.createElement('div');
+        this._elem = $(elem);
+        this._elem.addClass('jqplot-title');
+
+        if (!this.text) {
+            this.show = false;
+            this._elem.height(0);
+            this._elem.width(0);
+        }
+        else if (this.text) {
+            var color;
+            if (this.color) {
+                color = this.color;
+            }
+            else if (this.textColor) {
+                color = this.textColor;
+            }
+
+            // don't trust that a stylesheet is present, set the position.
+            var styles = {position:'absolute', top:'0px', left:'0px'};
+
+            if (this._plotWidth) {
+                styles['width'] = this._plotWidth+'px';
+            }
+            if (this.fontSize) {
+                styles['fontSize'] = this.fontSize;
+            }
+            if (typeof this.textAlign === 'string') {
+                styles['textAlign'] = this.textAlign;
+            }
+            else {
+                styles['textAlign'] = 'center';
+            }
+            if (color) {
+                styles['color'] = color;
+            }
+            if (this.paddingBottom) {
+                styles['paddingBottom'] = this.paddingBottom;
+            }
+            if (this.fontFamily) {
+                styles['fontFamily'] = this.fontFamily;
+            }
+
+            this._elem.css(styles);
+            if (this.escapeHtml) {
+                this._elem.text(this.text);
+            }
+            else {
+                this._elem.html(this.text);
+            }
+
+
+            // styletext += (this._plotWidth) ? 'width:'+this._plotWidth+'px;' : '';
+            // styletext += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
+            // styletext += (this.textAlign) ? 'text-align:'+this.textAlign+';' : 'text-align:center;';
+            // styletext += (color) ? 'color:'+color+';' : '';
+            // styletext += (this.paddingBottom) ? 'padding-bottom:'+this.paddingBottom+';' : '';
+            // this._elem = $('<div class="jqplot-title" style="'+styletext+'">'+this.text+'</div>');
+            // if (this.fontFamily) {
+            //     this._elem.css('font-family', this.fontFamily);
+            // }
+        }
+
+        elem = null;
+        
+        return this._elem;
+    };
+    
+    $.jqplot.DivTitleRenderer.prototype.pack = function() {
+        // nothing to do here
+    };
+  
+
+    var dotlen = 0.1;
+
+    $.jqplot.LinePattern = function (ctx, pattern) {
+
+        var defaultLinePatterns = {
+            dotted: [ dotlen, $.jqplot.config.dotGapLength ],
+            dashed: [ $.jqplot.config.dashLength, $.jqplot.config.gapLength ],
+            solid: null
+        };
+
+        if (typeof pattern === 'string') {
+            if (pattern[0] === '.' || pattern[0] === '-') {
+                var s = pattern;
+                pattern = [];
+                for (var i=0, imax=s.length; i<imax; i++) {
+                    if (s[i] === '.') {
+                        pattern.push( dotlen );
+                    }
+                    else if (s[i] === '-') {
+                        pattern.push( $.jqplot.config.dashLength );
+                    }
+                    else {
+                        continue;
+                    }
+                    pattern.push( $.jqplot.config.gapLength );
+                }
+            }
+            else {
+                pattern = defaultLinePatterns[pattern];
+            }
+        }
+
+        if (!(pattern && pattern.length)) {
+            return ctx;
+        }
+
+        var patternIndex = 0;
+        var patternDistance = pattern[0];
+        var px = 0;
+        var py = 0;
+        var pathx0 = 0;
+        var pathy0 = 0;
+
+        var moveTo = function (x, y) {
+            ctx.moveTo( x, y );
+            px = x;
+            py = y;
+            pathx0 = x;
+            pathy0 = y;
+        };
+
+        var lineTo = function (x, y) {
+            var scale = ctx.lineWidth;
+            var dx = x - px;
+            var dy = y - py;
+            var dist = Math.sqrt(dx*dx+dy*dy);
+            if ((dist > 0) && (scale > 0)) {
+                dx /= dist;
+                dy /= dist;
+                while (true) {
+                    var dp = scale * patternDistance;
+                    if (dp < dist) {
+                        px += dp * dx;
+                        py += dp * dy;
+                        if ((patternIndex & 1) == 0) {
+                            ctx.lineTo( px, py );
+                        }
+                        else {
+                            ctx.moveTo( px, py );
+                        }
+                        dist -= dp;
+                        patternIndex++;
+                        if (patternIndex >= pattern.length) {
+                            patternIndex = 0;
+                        }
+                        patternDistance = pattern[patternIndex];
+                    }
+                    else {
+                        px = x;
+                        py = y;
+                        if ((patternIndex & 1) == 0) {
+                            ctx.lineTo( px, py );
+                        }
+                        else {
+                            ctx.moveTo( px, py );
+                        }
+                        patternDistance -= dist / scale;
+                        break;
+                    }
+                }
+            }
+        };
+
+        var beginPath = function () {
+            ctx.beginPath();
+        };
+
+        var closePath = function () {
+            lineTo( pathx0, pathy0 );
+        };
+
+        return {
+            moveTo: moveTo,
+            lineTo: lineTo,
+            beginPath: beginPath,
+            closePath: closePath
+        };
+    };
+
+    // Class: $.jqplot.LineRenderer
+    // The default line renderer for jqPlot, this class has no options beyond the <Series> class.
+    // Draws series as a line.
+    $.jqplot.LineRenderer = function(){
+        this.shapeRenderer = new $.jqplot.ShapeRenderer();
+        this.shadowRenderer = new $.jqplot.ShadowRenderer();
+    };
+    
+    // called with scope of series.
+    $.jqplot.LineRenderer.prototype.init = function(options, plot) {
+        // Group: Properties
+        //
+        options = options || {};
+        this._type='line';
+        this.renderer.animation = {
+            show: false,
+            direction: 'left',
+            speed: 2500,
+            _supported: true
+        };
+        // prop: smooth
+        // True to draw a smoothed (interpolated) line through the data points
+        // with automatically computed number of smoothing points.
+        // Set to an integer number > 2 to specify number of smoothing points
+        // to use between each data point.
+        this.renderer.smooth = false;  // true or a number > 2 for smoothing.
+        this.renderer.tension = null; // null to auto compute or a number typically > 6.  Fewer points requires higher tension.
+        // prop: constrainSmoothing
+        // True to use a more accurate smoothing algorithm that will
+        // not overshoot any data points.  False to allow overshoot but
+        // produce a smoother looking line.
+        this.renderer.constrainSmoothing = true;
+        // this is smoothed data in grid coordinates, like gridData
+        this.renderer._smoothedData = [];
+        // this is smoothed data in plot units (plot coordinates), like plotData.
+        this.renderer._smoothedPlotData = [];
+        this.renderer._hiBandGridData = [];
+        this.renderer._lowBandGridData = [];
+        this.renderer._hiBandSmoothedData = [];
+        this.renderer._lowBandSmoothedData = [];
+
+        // prop: bandData
+        // Data used to draw error bands or confidence intervals above/below a line.
+        //
+        // bandData can be input in 3 forms.  jqPlot will figure out which is the
+        // low band line and which is the high band line for all forms:
+        // 
+        // A 2 dimensional array like [[yl1, yl2, ...], [yu1, yu2, ...]] where
+        // [yl1, yl2, ...] are y values of the lower line and
+        // [yu1, yu2, ...] are y values of the upper line.
+        // In this case there must be the same number of y data points as data points
+        // in the series and the bands will inherit the x values of the series.
+        //
+        // A 2 dimensional array like [[[xl1, yl1], [xl2, yl2], ...], [[xh1, yh1], [xh2, yh2], ...]]
+        // where [xl1, yl1] are x,y data points for the lower line and
+        // [xh1, yh1] are x,y data points for the high line.
+        // x values do not have to correspond to the x values of the series and can
+        // be of any arbitrary length.
+        //
+        // Can be of form [[yl1, yu1], [yl2, yu2], [yl3, yu3], ...] where
+        // there must be 3 or more arrays and there must be the same number of arrays
+        // as there are data points in the series.  In this case, 
+        // [yl1, yu1] specifies the lower and upper y values for the 1st
+        // data point and so on.  The bands will inherit the x
+        // values from the series.
+        this.renderer.bandData = [];
+
+        // Group: bands
+        // Banding around line, e.g error bands or confidence intervals.
+        this.renderer.bands = {
+            // prop: show
+            // true to show the bands.  If bandData or interval is
+            // supplied, show will be set to true by default.
+            show: false,
+            hiData: [],
+            lowData: [],
+            // prop: color
+            // color of lines at top and bottom of bands [default: series color].
+            color: this.color,
+            // prop: showLines
+            // True to show lines at top and bottom of bands [default: false].
+            showLines: false,
+            // prop: fill
+            // True to fill area between bands [default: true].
+            fill: true,
+            // prop: fillColor
+            // css color spec for filled area.  [default: series color].
+            fillColor: null,
+            _min: null,
+            _max: null,
+            // prop: interval
+            // User specified interval above and below line for bands [default: '3%''].
+            // Can be a value like 3 or a string like '3%' 
+            // or an upper/lower array like [1, -2] or ['2%', '-1.5%']
+            interval: '3%'
+        };
+
+
+        var lopts = {highlightMouseOver: options.highlightMouseOver, highlightMouseDown: options.highlightMouseDown, highlightColor: options.highlightColor};
+        
+        delete (options.highlightMouseOver);
+        delete (options.highlightMouseDown);
+        delete (options.highlightColor);
+        
+        $.extend(true, this.renderer, options);
+
+        this.renderer.options = options;
+
+        // if we are given some band data, and bands aren't explicity set to false in options, turn them on.
+        if (this.renderer.bandData.length > 1 && (!options.bands || options.bands.show == null)) {
+            this.renderer.bands.show = true;
+        }
+
+        // if we are given an interval, and bands aren't explicity set to false in options, turn them on.
+        else if (options.bands && options.bands.show == null && options.bands.interval != null) {
+            this.renderer.bands.show = true;
+        }
+
+        // if plot is filled, turn off bands.
+        if (this.fill) {
+            this.renderer.bands.show = false;
+        }
+
+        if (this.renderer.bands.show) {
+            this.renderer.initBands.call(this, this.renderer.options, plot);
+        }
+
+
+        // smoothing is not compatible with stacked lines, disable
+        if (this._stack) {
+            this.renderer.smooth = false;
+        }
+
+        // set the shape renderer options
+        var opts = {lineJoin:this.lineJoin, lineCap:this.lineCap, fill:this.fill, isarc:false, strokeStyle:this.color, fillStyle:this.fillColor, lineWidth:this.lineWidth, linePattern:this.linePattern, closePath:this.fill};
+        this.renderer.shapeRenderer.init(opts);
+
+        var shadow_offset = options.shadowOffset;
+        // set the shadow renderer options
+        if (shadow_offset == null) {
+            // scale the shadowOffset to the width of the line.
+            if (this.lineWidth > 2.5) {
+                shadow_offset = 1.25 * (1 + (Math.atan((this.lineWidth/2.5))/0.785398163 - 1)*0.6);
+                // var shadow_offset = this.shadowOffset;
+            }
+            // for skinny lines, don't make such a big shadow.
+            else {
+                shadow_offset = 1.25 * Math.atan((this.lineWidth/2.5))/0.785398163;
+            }
+        }
+        
+        var sopts = {lineJoin:this.lineJoin, lineCap:this.lineCap, fill:this.fill, isarc:false, angle:this.shadowAngle, offset:shadow_offset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.lineWidth, linePattern:this.linePattern, closePath:this.fill};
+        this.renderer.shadowRenderer.init(sopts);
+        this._areaPoints = [];
+        this._boundingBox = [[],[]];
+        
+        if (!this.isTrendline && this.fill || this.renderer.bands.show) {
+            // Group: Properties
+            //        
+            // prop: highlightMouseOver
+            // True to highlight area on a filled plot when moused over.
+            // This must be false to enable highlightMouseDown to highlight when clicking on an area on a filled plot.
+            this.highlightMouseOver = true;
+            // prop: highlightMouseDown
+            // True to highlight when a mouse button is pressed over an area on a filled plot.
+            // This will be disabled if highlightMouseOver is true.
+            this.highlightMouseDown = false;
+            // prop: highlightColor
+            // color to use when highlighting an area on a filled plot.
+            this.highlightColor = null;
+            // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
+            if (lopts.highlightMouseDown && lopts.highlightMouseOver == null) {
+                lopts.highlightMouseOver = false;
+            }
+        
+            $.extend(true, this, {highlightMouseOver: lopts.highlightMouseOver, highlightMouseDown: lopts.highlightMouseDown, highlightColor: lopts.highlightColor});
+            
+            if (!this.highlightColor) {
+                var fc = (this.renderer.bands.show) ? this.renderer.bands.fillColor : this.fillColor;
+                this.highlightColor = $.jqplot.computeHighlightColors(fc);
+            }
+            // turn off (disable) the highlighter plugin
+            if (this.highlighter) {
+                this.highlighter.show = false;
+            }
+        }
+        
+        if (!this.isTrendline && plot) {
+            plot.plugins.lineRenderer = {};
+            plot.postInitHooks.addOnce(postInit);
+            plot.postDrawHooks.addOnce(postPlotDraw);
+            plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
+            plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
+            plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
+            plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
+            plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
+        }
+
+    };
+
+    $.jqplot.LineRenderer.prototype.initBands = function(options, plot) {
+        // use bandData if no data specified in bands option
+        //var bd = this.renderer.bandData;
+        var bd = options.bandData || [];
+        var bands = this.renderer.bands;
+        bands.hiData = [];
+        bands.lowData = [];
+        var data = this.data;
+        bands._max = null;
+        bands._min = null;
+        // If 2 arrays, and each array greater than 2 elements, assume it is hi and low data bands of y values.
+        if (bd.length == 2) {
+            // Do we have an array of x,y values?
+            // like [[[1,1], [2,4], [3,3]], [[1,3], [2,6], [3,5]]]
+            if ($.isArray(bd[0][0])) {
+                // since an arbitrary array of points, spin through all of them to determine max and min lines.
+
+                var p;
+                var bdminidx = 0, bdmaxidx = 0;
+                for (var i = 0, l = bd[0].length; i<l; i++) {
+                    p = bd[0][i];
+                    if ((p[1] != null && p[1] > bands._max) || bands._max == null) {
+                        bands._max = p[1];
+                    }
+                    if ((p[1] != null && p[1] < bands._min) || bands._min == null) {
+                        bands._min = p[1];
+                    }
+                }
+                for (var i = 0, l = bd[1].length; i<l; i++) {
+                    p = bd[1][i];
+                    if ((p[1] != null && p[1] > bands._max) || bands._max == null) {
+                        bands._max = p[1];
+                        bdmaxidx = 1;
+                    }
+                    if ((p[1] != null && p[1] < bands._min) || bands._min == null) {
+                        bands._min = p[1];
+                        bdminidx = 1;
+                    }
+                }
+
+                if (bdmaxidx === bdminidx) {
+                    bands.show = false;
+                }
+
+                bands.hiData = bd[bdmaxidx];
+                bands.lowData = bd[bdminidx];
+            }
+            // else data is arrays of y values
+            // like [[1,4,3], [3,6,5]]
+            // must have same number of band data points as points in series
+            else if (bd[0].length === data.length && bd[1].length === data.length) {
+                var hi = (bd[0][0] > bd[1][0]) ? 0 : 1;
+                var low = (hi) ? 0 : 1;
+                for (var i=0, l=data.length; i < l; i++) {
+                    bands.hiData.push([data[i][0], bd[hi][i]]);
+                    bands.lowData.push([data[i][0], bd[low][i]]);
+                }
+            }
+
+            // we don't have proper data array, don't show bands.
+            else {
+                bands.show = false;
+            }
+        }
+
+        // if more than 2 arrays, have arrays of [ylow, yhi] values.
+        // note, can't distinguish case of [[ylow, yhi], [ylow, yhi]] from [[ylow, ylow], [yhi, yhi]]
+        // this is assumed to be of the latter form.
+        else if (bd.length > 2 && !$.isArray(bd[0][0])) {
+            var hi = (bd[0][0] > bd[0][1]) ? 0 : 1;
+            var low = (hi) ? 0 : 1;
+            for (var i=0, l=bd.length; i<l; i++) {
+                bands.hiData.push([data[i][0], bd[i][hi]]);
+                bands.lowData.push([data[i][0], bd[i][low]]);
+            }
+        }
+
+        // don't have proper data, auto calculate
+        else {
+            var intrv = bands.interval;
+            var a = null;
+            var b = null;
+            var afunc = null;
+            var bfunc = null;
+
+            if ($.isArray(intrv)) {
+                a = intrv[0];
+                b = intrv[1];
+            }
+            else {
+                a = intrv;
+            }
+
+            if (isNaN(a)) {
+                // we have a string
+                if (a.charAt(a.length - 1) === '%') {
+                    afunc = 'multiply';
+                    a = parseFloat(a)/100 + 1;
+                }
+            }
+
+            else {
+                a = parseFloat(a);
+                afunc = 'add';
+            }
+
+            if (b !== null && isNaN(b)) {
+                // we have a string
+                if (b.charAt(b.length - 1) === '%') {
+                    bfunc = 'multiply';
+                    b = parseFloat(b)/100 + 1;
+                }
+            }
+
+            else if (b !== null) {
+                b = parseFloat(b);
+                bfunc = 'add';
+            }
+
+            if (a !== null) {
+                if (b === null) {
+                    b = -a;
+                    bfunc = afunc;
+                    if (bfunc === 'multiply') {
+                        b += 2;
+                    }
+                }
+
+                // make sure a always applies to hi band.
+                if (a < b) {
+                    var temp = a;
+                    a = b;
+                    b = temp;
+                    temp = afunc;
+                    afunc = bfunc;
+                    bfunc = temp;
+                }
+
+                for (var i=0, l = data.length; i < l; i++) {
+                    switch (afunc) {
+                        case 'add':
+                            bands.hiData.push([data[i][0], data[i][1] + a]);
+                            break;
+                        case 'multiply':
+                            bands.hiData.push([data[i][0], data[i][1] * a]);
+                            break;
+                    }
+                    switch (bfunc) {
+                        case 'add':
+                            bands.lowData.push([data[i][0], data[i][1] + b]);
+                            break;
+                        case 'multiply':
+                            bands.lowData.push([data[i][0], data[i][1] * b]);
+                            break;
+                    }
+                }
+            }
+
+            else {
+                bands.show = false;
+            }
+        }
+
+        var hd = bands.hiData;
+        var ld = bands.lowData;
+        for (var i = 0, l = hd.length; i<l; i++) {
+            if ((hd[i][1] != null && hd[i][1] > bands._max) || bands._max == null) {
+                bands._max = hd[i][1];
+            }
+        }
+        for (var i = 0, l = ld.length; i<l; i++) {
+            if ((ld[i][1] != null && ld[i][1] < bands._min) || bands._min == null) {
+                bands._min = ld[i][1];
+            }
+        }
+
+        // one last check for proper data
+        // these don't apply any more since allowing arbitrary x,y values
+        // if (bands.hiData.length != bands.lowData.length) {
+        //     bands.show = false;
+        // }
+
+        // if (bands.hiData.length != this.data.length) {
+        //     bands.show = false;
+        // }
+
+        if (bands.fillColor === null) {
+            var c = $.jqplot.getColorComponents(bands.color);
+            // now adjust alpha to differentiate fill
+            c[3] = c[3] * 0.5;
+            bands.fillColor = 'rgba(' + c[0] +', '+ c[1] +', '+ c[2] +', '+ c[3] + ')';
+        }
+    };
+
+    function getSteps (d, f) {
+        return (3.4182054+f) * Math.pow(d, -0.3534992);
+    }
+
+    function computeSteps (d1, d2) {
+        var s = Math.sqrt(Math.pow((d2[0]- d1[0]), 2) + Math.pow ((d2[1] - d1[1]), 2));
+        return 5.7648 * Math.log(s) + 7.4456;
+    }
+
+    function tanh (x) {
+        var a = (Math.exp(2*x) - 1) / (Math.exp(2*x) + 1);
+        return a;
+    }
+
+    //////////
+    // computeConstrainedSmoothedData
+    // An implementation of the constrained cubic spline interpolation
+    // method as presented in:
+    //
+    // Kruger, CJC, Constrained Cubic Spine Interpolation for Chemical Engineering Applications
+    // http://www.korf.co.uk/spline.pdf
+    //
+    // The implementation below borrows heavily from the sample Visual Basic
+    // implementation by CJC Kruger found in http://www.korf.co.uk/spline.xls
+    //
+    /////////
+
+    // called with scope of series
+    function computeConstrainedSmoothedData (gd) {
+        var smooth = this.renderer.smooth;
+        var dim = this.canvas.getWidth();
+        var xp = this._xaxis.series_p2u;
+        var yp = this._yaxis.series_p2u; 
+        var steps =null;
+        var _steps = null;
+        var dist = gd.length/dim;
+        var _smoothedData = [];
+        var _smoothedPlotData = [];
+
+        if (!isNaN(parseFloat(smooth))) {
+            steps = parseFloat(smooth);
+        }
+        else {
+            steps = getSteps(dist, 0.5);
+        }
+
+        var yy = [];
+        var xx = [];
+
+        for (var i=0, l = gd.length; i<l; i++) {
+            yy.push(gd[i][1]);
+            xx.push(gd[i][0]);
+        }
+
+        function dxx(x1, x0) {
+            if (x1 - x0 == 0) {
+                return Math.pow(10,10);
+            }
+            else {
+                return x1 - x0;
+            }
+        }
+
+        var A, B, C, D;
+        // loop through each line segment.  Have # points - 1 line segments.  Nmber segments starting at 1.
+        var nmax = gd.length - 1;
+        for (var num = 1, gdl = gd.length; num<gdl; num++) {
+            var gxx = [];
+            var ggxx = [];
+            // point at each end of segment.
+            for (var j = 0; j < 2; j++) {
+                var i = num - 1 + j; // point number, 0 to # points.
+
+                if (i == 0 || i == nmax) {
+                    gxx[j] = Math.pow(10, 10);
+                }
+                else if (yy[i+1] - yy[i] == 0 || yy[i] - yy[i-1] == 0) {
+                    gxx[j] = 0;
+                }
+                else if (((xx[i+1] - xx[i]) / (yy[i+1] - yy[i]) + (xx[i] - xx[i-1]) / (yy[i] - yy[i-1])) == 0 ) {
+                    gxx[j] = 0;
+                }
+                else if ( (yy[i+1] - yy[i]) * (yy[i] - yy[i-1]) < 0 ) {
+                    gxx[j] = 0;
+                }
+
+                else {
+                    gxx[j] = 2 / (dxx(xx[i + 1], xx[i]) / (yy[i + 1] - yy[i]) + dxx(xx[i], xx[i - 1]) / (yy[i] - yy[i - 1]));
+                }
+            }
+
+            // Reset first derivative (slope) at first and last point
+            if (num == 1) {
+                // First point has 0 2nd derivative
+                gxx[0] = 3 / 2 * (yy[1] - yy[0]) / dxx(xx[1], xx[0]) - gxx[1] / 2;
+            }
+            else if (num == nmax) {
+                // Last point has 0 2nd derivative
+                gxx[1] = 3 / 2 * (yy[nmax] - yy[nmax - 1]) / dxx(xx[nmax], xx[nmax - 1]) - gxx[0] / 2;
+            }   
+
+            // Calc second derivative at points
+            ggxx[0] = -2 * (gxx[1] + 2 * gxx[0]) / dxx(xx[num], xx[num - 1]) + 6 * (yy[num] - yy[num - 1]) / Math.pow(dxx(xx[num], xx[num - 1]), 2);
+            ggxx[1] = 2 * (2 * gxx[1] + gxx[0]) / dxx(xx[num], xx[num - 1]) - 6 * (yy[num] - yy[num - 1]) / Math.pow(dxx(xx[num], xx[num - 1]), 2);
+
+            // Calc constants for cubic interpolation
+            D = 1 / 6 * (ggxx[1] - ggxx[0]) / dxx(xx[num], xx[num - 1]);
+            C = 1 / 2 * (xx[num] * ggxx[0] - xx[num - 1] * ggxx[1]) / dxx(xx[num], xx[num - 1]);
+            B = (yy[num] - yy[num - 1] - C * (Math.pow(xx[num], 2) - Math.pow(xx[num - 1], 2)) - D * (Math.pow(xx[num], 3) - Math.pow(xx[num - 1], 3))) / dxx(xx[num], xx[num - 1]);
+            A = yy[num - 1] - B * xx[num - 1] - C * Math.pow(xx[num - 1], 2) - D * Math.pow(xx[num - 1], 3);
+
+            var increment = (xx[num] - xx[num - 1]) / steps;
+            var temp, tempx;
+
+            for (var j = 0, l = steps; j < l; j++) {
+                temp = [];
+                tempx = xx[num - 1] + j * increment;
+                temp.push(tempx);
+                temp.push(A + B * tempx + C * Math.pow(tempx, 2) + D * Math.pow(tempx, 3));
+                _smoothedData.push(temp);
+                _smoothedPlotData.push([xp(temp[0]), yp(temp[1])]);
+            }
+        }
+
+        _smoothedData.push(gd[i]);
+        _smoothedPlotData.push([xp(gd[i][0]), yp(gd[i][1])]);
+
+        return [_smoothedData, _smoothedPlotData];
+    }
+
+    ///////
+    // computeHermiteSmoothedData
+    // A hermite spline smoothing of the plot data.
+    // This implementation is derived from the one posted
+    // by krypin on the jqplot-users mailing list:
+    //
+    // http://groups.google.com/group/jqplot-users/browse_thread/thread/748be6a445723cea?pli=1
+    //
+    // with a blog post:
+    //
+    // http://blog.statscollector.com/a-plugin-renderer-for-jqplot-to-draw-a-hermite-spline/
+    //
+    // and download of the original plugin:
+    //
+    // http://blog.statscollector.com/wp-content/uploads/2010/02/jqplot.hermiteSplineRenderer.js
+    //////////
+
+    // called with scope of series
+    function computeHermiteSmoothedData (gd) {
+        var smooth = this.renderer.smooth;
+        var tension = this.renderer.tension;
+        var dim = this.canvas.getWidth();
+        var xp = this._xaxis.series_p2u;
+        var yp = this._yaxis.series_p2u; 
+        var steps =null;
+        var _steps = null;
+        var a = null;
+        var a1 = null;
+        var a2 = null;
+        var slope = null;
+        var slope2 = null;
+        var temp = null;
+        var t, s, h1, h2, h3, h4;
+        var TiX, TiY, Ti1X, Ti1Y;
+        var pX, pY, p;
+        var sd = [];
+        var spd = [];
+        var dist = gd.length/dim;
+        var min, max, stretch, scale, shift;
+        var _smoothedData = [];
+        var _smoothedPlotData = [];
+        if (!isNaN(parseFloat(smooth))) {
+            steps = parseFloat(smooth);
+        }
+        else {
+            steps = getSteps(dist, 0.5);
+        }
+        if (!isNaN(parseFloat(tension))) {
+            tension = parseFloat(tension);
+        }
+
+        for (var i=0, l = gd.length-1; i < l; i++) {
+
+            if (tension === null) {
+                slope = Math.abs((gd[i+1][1] - gd[i][1]) / (gd[i+1][0] - gd[i][0]));
+
+                min = 0.3;
+                max = 0.6;
+                stretch = (max - min)/2.0;
+                scale = 2.5;
+                shift = -1.4;
+
+                temp = slope/scale + shift;
+
+                a1 = stretch * tanh(temp) - stretch * tanh(shift) + min;
+
+                // if have both left and right line segments, will use  minimum tension. 
+                if (i > 0) {
+                    slope2 = Math.abs((gd[i][1] - gd[i-1][1]) / (gd[i][0] - gd[i-1][0]));
+                }
+                temp = slope2/scale + shift;
+
+                a2 = stretch * tanh(temp) - stretch * tanh(shift) + min;
+
+                a = (a1 + a2)/2.0;
+
+            }
+            else {
+                a = tension;
+            }
+            for (t=0; t < steps; t++) {
+                s = t / steps;
+                h1 = (1 + 2*s)*Math.pow((1-s),2);
+                h2 = s*Math.pow((1-s),2);
+                h3 = Math.pow(s,2)*(3-2*s);
+                h4 = Math.pow(s,2)*(s-1);     
+                
+                if (gd[i-1]) {  
+                    TiX = a * (gd[i+1][0] - gd[i-1][0]); 
+                    TiY = a * (gd[i+1][1] - gd[i-1][1]);
+                } else {
+                    TiX = a * (gd[i+1][0] - gd[i][0]); 
+                    TiY = a * (gd[i+1][1] - gd[i][1]);                                  
+                }
+                if (gd[i+2]) {  
+                    Ti1X = a * (gd[i+2][0] - gd[i][0]); 
+                    Ti1Y = a * (gd[i+2][1] - gd[i][1]);
+                } else {
+                    Ti1X = a * (gd[i+1][0] - gd[i][0]); 
+                    Ti1Y = a * (gd[i+1][1] - gd[i][1]);                                 
+                }
+                
+                pX = h1*gd[i][0] + h3*gd[i+1][0] + h2*TiX + h4*Ti1X;
+                pY = h1*gd[i][1] + h3*gd[i+1][1] + h2*TiY + h4*Ti1Y;
+                p = [pX, pY];
+
+                _smoothedData.push(p);
+                _smoothedPlotData.push([xp(pX), yp(pY)]);
+            }
+        }
+        _smoothedData.push(gd[l]);
+        _smoothedPlotData.push([xp(gd[l][0]), yp(gd[l][1])]);
+
+        return [_smoothedData, _smoothedPlotData];
+    }
+    
+    // setGridData
+    // converts the user data values to grid coordinates and stores them
+    // in the gridData array.
+    // Called with scope of a series.
+    $.jqplot.LineRenderer.prototype.setGridData = function(plot) {
+        // recalculate the grid data
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        var data = this._plotData;
+        var pdata = this._prevPlotData;
+        this.gridData = [];
+        this._prevGridData = [];
+        this.renderer._smoothedData = [];
+        this.renderer._smoothedPlotData = [];
+        this.renderer._hiBandGridData = [];
+        this.renderer._lowBandGridData = [];
+        this.renderer._hiBandSmoothedData = [];
+        this.renderer._lowBandSmoothedData = [];
+        var bands = this.renderer.bands;
+        var hasNull = false;
+        for (var i=0, l=data.length; i < l; i++) {
+            // if not a line series or if no nulls in data, push the converted point onto the array.
+            if (data[i][0] != null && data[i][1] != null) {
+                this.gridData.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
+            }
+            // else if there is a null, preserve it.
+            else if (data[i][0] == null) {
+                hasNull = true;
+                this.gridData.push([null, yp.call(this._yaxis, data[i][1])]);
+            }
+            else if (data[i][1] == null) {
+                hasNull = true;
+                this.gridData.push([xp.call(this._xaxis, data[i][0]), null]);
+            }
+            // if not a line series or if no nulls in data, push the converted point onto the array.
+            if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] != null) {
+                this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), yp.call(this._yaxis, pdata[i][1])]);
+            }
+            // else if there is a null, preserve it.
+            else if (pdata[i] != null && pdata[i][0] == null) {
+                this._prevGridData.push([null, yp.call(this._yaxis, pdata[i][1])]);
+            }  
+            else if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] == null) {
+                this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), null]);
+            }
+        }
+
+        // don't do smoothing or bands on broken lines.
+        if (hasNull) {
+            this.renderer.smooth = false;
+            if (this._type === 'line') {
+                bands.show = false;
+            }
+        }
+
+        if (this._type === 'line' && bands.show) {
+            for (var i=0, l=bands.hiData.length; i<l; i++) {
+                this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]);
+            }
+            for (var i=0, l=bands.lowData.length; i<l; i++) {
+                this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]);
+            }
+        }
+
+        // calculate smoothed data if enough points and no nulls
+        if (this._type === 'line' && this.renderer.smooth && this.gridData.length > 2) {
+            var ret;
+            if (this.renderer.constrainSmoothing) {
+                ret = computeConstrainedSmoothedData.call(this, this.gridData);
+                this.renderer._smoothedData = ret[0];
+                this.renderer._smoothedPlotData = ret[1];
+
+                if (bands.show) {
+                    ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData);
+                    this.renderer._hiBandSmoothedData = ret[0];
+                    ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData);
+                    this.renderer._lowBandSmoothedData = ret[0];
+                }
+
+                ret = null;
+            }
+            else {
+                ret = computeHermiteSmoothedData.call(this, this.gridData);
+                this.renderer._smoothedData = ret[0];
+                this.renderer._smoothedPlotData = ret[1];
+
+                if (bands.show) {
+                    ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData);
+                    this.renderer._hiBandSmoothedData = ret[0];
+                    ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData);
+                    this.renderer._lowBandSmoothedData = ret[0];
+                }
+
+                ret = null;
+            }
+        }
+    };
+    
+    // makeGridData
+    // converts any arbitrary data values to grid coordinates and
+    // returns them.  This method exists so that plugins can use a series'
+    // linerenderer to generate grid data points without overwriting the
+    // grid data associated with that series.
+    // Called with scope of a series.
+    $.jqplot.LineRenderer.prototype.makeGridData = function(data, plot) {
+        // recalculate the grid data
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        var gd = [];
+        var pgd = [];
+        this.renderer._smoothedData = [];
+        this.renderer._smoothedPlotData = [];
+        this.renderer._hiBandGridData = [];
+        this.renderer._lowBandGridData = [];
+        this.renderer._hiBandSmoothedData = [];
+        this.renderer._lowBandSmoothedData = [];
+        var bands = this.renderer.bands;
+        var hasNull = false;
+        for (var i=0; i<data.length; i++) {
+            // if not a line series or if no nulls in data, push the converted point onto the array.
+            if (data[i][0] != null && data[i][1] != null) {
+                gd.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
+            }
+            // else if there is a null, preserve it.
+            else if (data[i][0] == null) {
+                hasNull = true;
+                gd.push([null, yp.call(this._yaxis, data[i][1])]);
+            }
+            else if (data[i][1] == null) {
+                hasNull = true;
+                gd.push([xp.call(this._xaxis, data[i][0]), null]);
+            }
+        }
+
+        // don't do smoothing or bands on broken lines.
+        if (hasNull) {
+            this.renderer.smooth = false;
+            if (this._type === 'line') {
+                bands.show = false;
+            }
+        }
+
+        if (this._type === 'line' && bands.show) {
+            for (var i=0, l=bands.hiData.length; i<l; i++) {
+                this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]);
+            }
+            for (var i=0, l=bands.lowData.length; i<l; i++) {
+                this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]);
+            }
+        }
+
+        if (this._type === 'line' && this.renderer.smooth && gd.length > 2) {
+            var ret;
+            if (this.renderer.constrainSmoothing) {
+                ret = computeConstrainedSmoothedData.call(this, gd);
+                this.renderer._smoothedData = ret[0];
+                this.renderer._smoothedPlotData = ret[1];
+
+                if (bands.show) {
+                    ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData);
+                    this.renderer._hiBandSmoothedData = ret[0];
+                    ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData);
+                    this.renderer._lowBandSmoothedData = ret[0];
+                }
+
+                ret = null;
+            }
+            else {
+                ret = computeHermiteSmoothedData.call(this, gd);
+                this.renderer._smoothedData = ret[0];
+                this.renderer._smoothedPlotData = ret[1];
+
+                if (bands.show) {
+                    ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData);
+                    this.renderer._hiBandSmoothedData = ret[0];
+                    ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData);
+                    this.renderer._lowBandSmoothedData = ret[0];
+                }
+
+                ret = null;
+            }
+        }
+        return gd;
+    };
+    
+
+    // called within scope of series.
+    $.jqplot.LineRenderer.prototype.draw = function(ctx, gd, options, plot) {
+        var i;
+        // get a copy of the options, so we don't modify the original object.
+        var opts = $.extend(true, {}, options);
+        var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+        var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
+        var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+        var fillAndStroke = (opts.fillAndStroke != undefined) ? opts.fillAndStroke : this.fillAndStroke;
+        var xmin, ymin, xmax, ymax;
+        ctx.save();
+        if (gd.length) {
+            if (showLine) {
+                // if we fill, we'll have to add points to close the curve.
+                if (fill) {
+                    if (this.fillToZero) { 
+                        // have to break line up into shapes at axis crossings
+                        var negativeColor = this.negativeColor;
+                        if (! this.useNegativeColors) {
+                            negativeColor = opts.fillStyle;
+                        }
+                        var isnegative = false;
+                        var posfs = opts.fillStyle;
+                    
+                        // if stoking line as well as filling, get a copy of line data.
+                        if (fillAndStroke) {
+                            var fasgd = gd.slice(0);
+                        }
+                        // if not stacked, fill down to axis
+                        if (this.index == 0 || !this._stack) {
+                        
+                            var tempgd = [];
+                            var pd = (this.renderer.smooth) ? this.renderer._smoothedPlotData : this._plotData;
+                            this._areaPoints = [];
+                            var pyzero = this._yaxis.series_u2p(this.fillToValue);
+                            var pxzero = this._xaxis.series_u2p(this.fillToValue);
+
+                            opts.closePath = true;
+                            
+                            if (this.fillAxis == 'y') {
+                                tempgd.push([gd[0][0], pyzero]);
+                                this._areaPoints.push([gd[0][0], pyzero]);
+                                
+                                for (var i=0; i<gd.length-1; i++) {
+                                    tempgd.push(gd[i]);
+                                    this._areaPoints.push(gd[i]);
+                                    // do we have an axis crossing?
+                                    if (pd[i][1] * pd[i+1][1] <= 0) {
+                                        if (pd[i][1] < 0) {
+                                            isnegative = true;
+                                            opts.fillStyle = negativeColor;
+                                        }
+                                        else {
+                                            isnegative = false;
+                                            opts.fillStyle = posfs;
+                                        }
+                                        
+                                        var xintercept = gd[i][0] + (gd[i+1][0] - gd[i][0]) * (pyzero-gd[i][1])/(gd[i+1][1] - gd[i][1]);
+                                        tempgd.push([xintercept, pyzero]);
+                                        this._areaPoints.push([xintercept, pyzero]);
+                                        // now draw this shape and shadow.
+                                        if (shadow) {
+                                            this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
+                                        }
+                                        this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
+                                        // now empty temp array and continue
+                                        tempgd = [[xintercept, pyzero]];
+                                        // this._areaPoints = [[xintercept, pyzero]];
+                                    }   
+                                }
+                                if (pd[gd.length-1][1] < 0) {
+                                    isnegative = true;
+                                    opts.fillStyle = negativeColor;
+                                }
+                                else {
+                                    isnegative = false;
+                                    opts.fillStyle = posfs;
+                                }
+                                tempgd.push(gd[gd.length-1]);
+                                this._areaPoints.push(gd[gd.length-1]);
+                                tempgd.push([gd[gd.length-1][0], pyzero]); 
+                                this._areaPoints.push([gd[gd.length-1][0], pyzero]); 
+                            }
+                            // now draw the last area.
+                            if (shadow) {
+                                this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
+                            }
+                            this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
+                            
+                            
+                            // var gridymin = this._yaxis.series_u2p(0);
+                            // // IE doesn't return new length on unshift
+                            // gd.unshift([gd[0][0], gridymin]);
+                            // len = gd.length;
+                            // gd.push([gd[len - 1][0], gridymin]);                   
+                        }
+                        // if stacked, fill to line below 
+                        else {
+                            var prev = this._prevGridData;
+                            for (var i=prev.length; i>0; i--) {
+                                gd.push(prev[i-1]);
+                                // this._areaPoints.push(prev[i-1]);
+                            }
+                            if (shadow) {
+                                this.renderer.shadowRenderer.draw(ctx, gd, opts);
+                            }
+                            this._areaPoints = gd;
+                            this.renderer.shapeRenderer.draw(ctx, gd, opts);
+                        }
+                    }
+                    /////////////////////////
+                    // Not filled to zero
+                    ////////////////////////
+                    else {                    
+                        // if stoking line as well as filling, get a copy of line data.
+                        if (fillAndStroke) {
+                            var fasgd = gd.slice(0);
+                        }
+                        // if not stacked, fill down to axis
+                        if (this.index == 0 || !this._stack) {
+                            // var gridymin = this._yaxis.series_u2p(this._yaxis.min) - this.gridBorderWidth / 2;
+                            var gridymin = ctx.canvas.height;
+                            // IE doesn't return new length on unshift
+                            gd.unshift([gd[0][0], gridymin]);
+                            var len = gd.length;
+                            gd.push([gd[len - 1][0], gridymin]);                   
+                        }
+                        // if stacked, fill to line below 
+                        else {
+                            var prev = this._prevGridData;
+                            for (var i=prev.length; i>0; i--) {
+                                gd.push(prev[i-1]);
+                            }
+                        }
+                        this._areaPoints = gd;
+                        
+                        if (shadow) {
+                            this.renderer.shadowRenderer.draw(ctx, gd, opts);
+                        }
+            
+                        this.renderer.shapeRenderer.draw(ctx, gd, opts);                        
+                    }
+                    if (fillAndStroke) {
+                        var fasopts = $.extend(true, {}, opts, {fill:false, closePath:false});
+                        this.renderer.shapeRenderer.draw(ctx, fasgd, fasopts);
+                        //////////
+                        // TODO: figure out some way to do shadows nicely
+                        // if (shadow) {
+                        //     this.renderer.shadowRenderer.draw(ctx, fasgd, fasopts);
+                        // }
+                        // now draw the markers
+                        if (this.markerRenderer.show) {
+                            if (this.renderer.smooth) {
+                                fasgd = this.gridData;
+                            }
+                            for (i=0; i<fasgd.length; i++) {
+                                this.markerRenderer.draw(fasgd[i][0], fasgd[i][1], ctx, opts.markerOptions);
+                            }
+                        }
+                    }
+                }
+                else {
+
+                    if (this.renderer.bands.show) {
+                        var bdat;
+                        var bopts = $.extend(true, {}, opts);
+                        if (this.renderer.bands.showLines) {
+                            bdat = (this.renderer.smooth) ? this.renderer._hiBandSmoothedData : this.renderer._hiBandGridData;
+                            this.renderer.shapeRenderer.draw(ctx, bdat, opts);
+                            bdat = (this.renderer.smooth) ? this.renderer._lowBandSmoothedData : this.renderer._lowBandGridData;
+                            this.renderer.shapeRenderer.draw(ctx, bdat, bopts);
+                        }
+
+                        if (this.renderer.bands.fill) {
+                            if (this.renderer.smooth) {
+                                bdat = this.renderer._hiBandSmoothedData.concat(this.renderer._lowBandSmoothedData.reverse());
+                            }
+                            else {
+                                bdat = this.renderer._hiBandGridData.concat(this.renderer._lowBandGridData.reverse());
+                            }
+                            this._areaPoints = bdat;
+                            bopts.closePath = true;
+                            bopts.fill = true;
+                            bopts.fillStyle = this.renderer.bands.fillColor;
+                            this.renderer.shapeRenderer.draw(ctx, bdat, bopts);
+                        }
+                    }
+
+                    if (shadow) {
+                        this.renderer.shadowRenderer.draw(ctx, gd, opts);
+                    }
+    
+                    this.renderer.shapeRenderer.draw(ctx, gd, opts);
+                }
+            }
+            // calculate the bounding box
+            var xmin = xmax = ymin = ymax = null;
+            for (i=0; i<this._areaPoints.length; i++) {
+                var p = this._areaPoints[i];
+                if (xmin > p[0] || xmin == null) {
+                    xmin = p[0];
+                }
+                if (ymax < p[1] || ymax == null) {
+                    ymax = p[1];
+                }
+                if (xmax < p[0] || xmax == null) {
+                    xmax = p[0];
+                }
+                if (ymin > p[1] || ymin == null) {
+                    ymin = p[1];
+                }
+            }
+
+            if (this.type === 'line' && this.renderer.bands.show) {
+                ymax = this._yaxis.series_u2p(this.renderer.bands._min);
+                ymin = this._yaxis.series_u2p(this.renderer.bands._max);
+            }
+
+            this._boundingBox = [[xmin, ymax], [xmax, ymin]];
+        
+            // now draw the markers
+            if (this.markerRenderer.show && !fill) {
+                if (this.renderer.smooth) {
+                    gd = this.gridData;
+                }
+                for (i=0; i<gd.length; i++) {
+                    if (gd[i][0] != null && gd[i][1] != null) {
+                        this.markerRenderer.draw(gd[i][0], gd[i][1], ctx, opts.markerOptions);
+                    }
+                }
+            }
+        }
+        
+        ctx.restore();
+    };  
+    
+    $.jqplot.LineRenderer.prototype.drawShadow = function(ctx, gd, options) {
+        // This is a no-op, shadows drawn with lines.
+    };
+    
+    // called with scope of plot.
+    // make sure to not leave anything highlighted.
+    function postInit(target, data, options) {
+        for (var i=0; i<this.series.length; i++) {
+            if (this.series[i].renderer.constructor == $.jqplot.LineRenderer) {
+                // don't allow mouseover and mousedown at same time.
+                if (this.series[i].highlightMouseOver) {
+                    this.series[i].highlightMouseDown = false;
+                }
+            }
+        }
+    }  
+    
+    // called within context of plot
+    // create a canvas which we can draw on.
+    // insert it before the eventCanvas, so eventCanvas will still capture events.
+    function postPlotDraw() {
+        // Memory Leaks patch    
+        if (this.plugins.lineRenderer && this.plugins.lineRenderer.highlightCanvas) {
+          this.plugins.lineRenderer.highlightCanvas.resetCanvas();
+          this.plugins.lineRenderer.highlightCanvas = null;
+        }
+        
+        this.plugins.lineRenderer.highlightedSeriesIndex = null;
+        this.plugins.lineRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
+        
+        this.eventCanvas._elem.before(this.plugins.lineRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-lineRenderer-highlight-canvas', this._plotDimensions, this));
+        this.plugins.lineRenderer.highlightCanvas.setContext();
+        this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
+    } 
+    
+    function highlight (plot, sidx, pidx, points) {
+        var s = plot.series[sidx];
+        var canvas = plot.plugins.lineRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        s._highlightedPoint = pidx;
+        plot.plugins.lineRenderer.highlightedSeriesIndex = sidx;
+        var opts = {fillStyle: s.highlightColor};
+        if (s.type === 'line' && s.renderer.bands.show) {
+            opts.fill = true;
+            opts.closePath = true;
+        }
+        s.renderer.shapeRenderer.draw(canvas._ctx, points, opts);
+        canvas = null;
+    }
+    
+    function unhighlight (plot) {
+        var canvas = plot.plugins.lineRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        for (var i=0; i<plot.series.length; i++) {
+            plot.series[i]._highlightedPoint = null;
+        }
+        plot.plugins.lineRenderer.highlightedSeriesIndex = null;
+        plot.target.trigger('jqplotDataUnhighlight');
+        canvas = null;
+    }
+    
+    
+    function handleMove(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var evt1 = jQuery.Event('jqplotDataMouseOver');
+            evt1.pageX = ev.pageX;
+            evt1.pageY = ev.pageY;
+            plot.target.trigger(evt1, ins);
+            if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    }
+    
+    function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    }
+    
+    function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
+        var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
+        if (idx != null && plot.series[idx].highlightMouseDown) {
+            unhighlight(plot);
+        }
+    }
+    
+    function handleClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var evt = jQuery.Event('jqplotDataClick');
+            evt.which = ev.which;
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            plot.target.trigger(evt, ins);
+        }
+    }
+    
+    function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
+            if (idx != null && plot.series[idx].highlightMouseDown) {
+                unhighlight(plot);
+            }
+            var evt = jQuery.Event('jqplotDataRightClick');
+            evt.which = ev.which;
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            plot.target.trigger(evt, ins);
+        }
+    }
+    
+    
+    // class: $.jqplot.LinearAxisRenderer
+    // The default jqPlot axis renderer, creating a numeric axis.
+    $.jqplot.LinearAxisRenderer = function() {
+    };
+    
+    // called with scope of axis object.
+    $.jqplot.LinearAxisRenderer.prototype.init = function(options){
+        // prop: breakPoints
+        // EXPERIMENTAL!! Use at your own risk!
+        // Works only with linear axes and the default tick renderer.
+        // Array of [start, stop] points to create a broken axis.
+        // Broken axes have a "jump" in them, which is an immediate 
+        // transition from a smaller value to a larger value.
+        // Currently, axis ticks MUST be manually assigned if using breakPoints
+        // by using the axis ticks array option.
+        this.breakPoints = null;
+        // prop: breakTickLabel
+        // Label to use at the axis break if breakPoints are specified.
+        this.breakTickLabel = "&asymp;";
+        // prop: drawBaseline
+        // True to draw the axis baseline.
+        this.drawBaseline = true;
+        // prop: baselineWidth
+        // width of the baseline in pixels.
+        this.baselineWidth = null;
+        // prop: baselineColor
+        // CSS color spec for the baseline.
+        this.baselineColor = null;
+        // prop: forceTickAt0
+        // This will ensure that there is always a tick mark at 0.
+        // If data range is strictly positive or negative,
+        // this will force 0 to be inside the axis bounds unless
+        // the appropriate axis pad (pad, padMin or padMax) is set
+        // to 0, then this will force an axis min or max value at 0.
+        // This has know effect when any of the following options
+        // are set:  autoscale, min, max, numberTicks or tickInterval.
+        this.forceTickAt0 = false;
+        // prop: forceTickAt100
+        // This will ensure that there is always a tick mark at 100.
+        // If data range is strictly above or below 100,
+        // this will force 100 to be inside the axis bounds unless
+        // the appropriate axis pad (pad, padMin or padMax) is set
+        // to 0, then this will force an axis min or max value at 100.
+        // This has know effect when any of the following options
+        // are set:  autoscale, min, max, numberTicks or tickInterval.
+        this.forceTickAt100 = false;
+        // prop: tickInset
+        // Controls the amount to inset the first and last ticks from 
+        // the edges of the grid, in multiples of the tick interval.
+        // 0 is no inset, 0.5 is one half a tick interval, 1 is a full
+        // tick interval, etc.
+        this.tickInset = 0;
+        // prop: minorTicks
+        // Number of ticks to add between "major" ticks.
+        // Major ticks are ticks supplied by user or auto computed.
+        // Minor ticks cannot be created by user.
+        this.minorTicks = 0;
+        // prop: alignTicks
+        // true to align tick marks across opposed axes
+        // such as from the y2axis to yaxis.
+        this.alignTicks = false;
+        this._autoFormatString = '';
+        this._overrideFormatString = false;
+        this._scalefact = 1.0;
+        $.extend(true, this, options);
+        if (this.breakPoints) {
+            if (!$.isArray(this.breakPoints)) {
+                this.breakPoints = null;
+            }
+            else if (this.breakPoints.length < 2 || this.breakPoints[1] <= this.breakPoints[0]) {
+                this.breakPoints = null;
+            }
+        }
+        if (this.numberTicks != null && this.numberTicks < 2) {
+            this.numberTicks = 2;
+        }
+        this.resetDataBounds();
+    };
+    
+    // called with scope of axis
+    $.jqplot.LinearAxisRenderer.prototype.draw = function(ctx, plot) {
+        if (this.show) {
+            // populate the axis label and value properties.
+            // createTicks is a method on the renderer, but
+            // call it within the scope of the axis.
+            this.renderer.createTicks.call(this, plot);
+            // fill a div with axes labels in the right direction.
+            // Need to pregenerate each axis to get its bounds and
+            // position it and the labels correctly on the plot.
+            var dim=0;
+            var temp;
+            // Added for theming.
+            if (this._elem) {
+                // Memory Leaks patch
+                //this._elem.empty();
+                this._elem.emptyForce();
+                this._elem = null;
+            }
+            
+            this._elem = $(document.createElement('div'));
+            this._elem.addClass('jqplot-axis jqplot-'+this.name);
+            this._elem.css('position', 'absolute');
+
+            
+            if (this.name == 'xaxis' || this.name == 'x2axis') {
+                this._elem.width(this._plotDimensions.width);
+            }
+            else {
+                this._elem.height(this._plotDimensions.height);
+            }
+            
+            // create a _label object.
+            this.labelOptions.axis = this.name;
+            this._label = new this.labelRenderer(this.labelOptions);
+            if (this._label.show) {
+                var elem = this._label.draw(ctx, plot);
+                elem.appendTo(this._elem);
+                elem = null;
+            }
+    
+            var t = this._ticks;
+            var tick;
+            for (var i=0; i<t.length; i++) {
+                tick = t[i];
+                if (tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
+                    this._elem.append(tick.draw(ctx, plot));
+                }
+            }
+            tick = null;
+            t = null;
+        }
+        return this._elem;
+    };
+    
+    // called with scope of an axis
+    $.jqplot.LinearAxisRenderer.prototype.reset = function() {
+        this.min = this._options.min;
+        this.max = this._options.max;
+        this.tickInterval = this._options.tickInterval;
+        this.numberTicks = this._options.numberTicks;
+        this._autoFormatString = '';
+        if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) {
+            this.tickOptions.formatString = '';
+        }
+
+        // this._ticks = this.__ticks;
+    };
+    
+    // called with scope of axis
+    $.jqplot.LinearAxisRenderer.prototype.set = function() { 
+        var dim = 0;
+        var temp;
+        var w = 0;
+        var h = 0;
+        var lshow = (this._label == null) ? false : this._label.show;
+        if (this.show) {
+            var t = this._ticks;
+            var tick;
+            for (var i=0; i<t.length; i++) {
+                tick = t[i];
+                if (!tick._breakTick && tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
+                    if (this.name == 'xaxis' || this.name == 'x2axis') {
+                        temp = tick._elem.outerHeight(true);
+                    }
+                    else {
+                        temp = tick._elem.outerWidth(true);
+                    }
+                    if (temp > dim) {
+                        dim = temp;
+                    }
+                }
+            }
+            tick = null;
+            t = null;
+            
+            if (lshow) {
+                w = this._label._elem.outerWidth(true);
+                h = this._label._elem.outerHeight(true); 
+            }
+            if (this.name == 'xaxis') {
+                dim = dim + h;
+                this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
+            }
+            else if (this.name == 'x2axis') {
+                dim = dim + h;
+                this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
+            }
+            else if (this.name == 'yaxis') {
+                dim = dim + w;
+                this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
+                if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+                    this._label._elem.css('width', w+'px');
+                }
+            }
+            else {
+                dim = dim + w;
+                this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
+                if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+                    this._label._elem.css('width', w+'px');
+                }
+            }
+        }  
+    };    
+    
+    // called with scope of axis
+    $.jqplot.LinearAxisRenderer.prototype.createTicks = function(plot) {
+        // we're are operating on an axis here
+        var ticks = this._ticks;
+        var userTicks = this.ticks;
+        var name = this.name;
+        // databounds were set on axis initialization.
+        var db = this._dataBounds;
+        var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
+        var interval;
+        var min, max;
+        var pos1, pos2;
+        var tt, i;
+        // get a copy of user's settings for min/max.
+        var userMin = this.min;
+        var userMax = this.max;
+        var userNT = this.numberTicks;
+        var userTI = this.tickInterval;
+
+        var threshold = 30;
+        this._scalefact =  (Math.max(dim, threshold+1) - threshold)/300.0;
+        
+        // if we already have ticks, use them.
+        // ticks must be in order of increasing value.
+        
+        if (userTicks.length) {
+            // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
+            for (i=0; i<userTicks.length; i++){
+                var ut = userTicks[i];
+                var t = new this.tickRenderer(this.tickOptions);
+                if ($.isArray(ut)) {
+                    t.value = ut[0];
+                    if (this.breakPoints) {
+                        if (ut[0] == this.breakPoints[0]) {
+                            t.label = this.breakTickLabel;
+                            t._breakTick = true;
+                            t.showGridline = false;
+                            t.showMark = false;
+                        }
+                        else if (ut[0] > this.breakPoints[0] && ut[0] <= this.breakPoints[1]) {
+                            t.show = false;
+                            t.showGridline = false;
+                            t.label = ut[1];
+                        }
+                        else {
+                            t.label = ut[1];
+                        }
+                    }
+                    else {
+                        t.label = ut[1];
+                    }
+                    t.setTick(ut[0], this.name);
+                    this._ticks.push(t);
+                }
+
+                else if ($.isPlainObject(ut)) {
+                    $.extend(true, t, ut);
+                    t.axis = this.name;
+                    this._ticks.push(t);
+                }
+                
+                else {
+                    t.value = ut;
+                    if (this.breakPoints) {
+                        if (ut == this.breakPoints[0]) {
+                            t.label = this.breakTickLabel;
+                            t._breakTick = true;
+                            t.showGridline = false;
+                            t.showMark = false;
+                        }
+                        else if (ut > this.breakPoints[0] && ut <= this.breakPoints[1]) {
+                            t.show = false;
+                            t.showGridline = false;
+                        }
+                    }
+                    t.setTick(ut, this.name);
+                    this._ticks.push(t);
+                }
+            }
+            this.numberTicks = userTicks.length;
+            this.min = this._ticks[0].value;
+            this.max = this._ticks[this.numberTicks-1].value;
+            this.tickInterval = (this.max - this.min) / (this.numberTicks - 1);
+        }
+        
+        // we don't have any ticks yet, let's make some!
+        else {
+            if (name == 'xaxis' || name == 'x2axis') {
+                dim = this._plotDimensions.width;
+            }
+            else {
+                dim = this._plotDimensions.height;
+            }
+
+            var _numberTicks = this.numberTicks;
+
+            // if aligning this axis, use number of ticks from previous axis.
+            // Do I need to reset somehow if alignTicks is changed and then graph is replotted??
+            if (this.alignTicks) {
+                if (this.name === 'x2axis' && plot.axes.xaxis.show) {
+                    _numberTicks = plot.axes.xaxis.numberTicks;
+                }
+                else if (this.name.charAt(0) === 'y' && this.name !== 'yaxis' && this.name !== 'yMidAxis' && plot.axes.yaxis.show) {
+                    _numberTicks = plot.axes.yaxis.numberTicks;
+                }
+            }
+        
+            min = ((this.min != null) ? this.min : db.min);
+            max = ((this.max != null) ? this.max : db.max);
+
+            var range = max - min;
+            var rmin, rmax;
+            var temp;
+
+            if (this.tickOptions == null || !this.tickOptions.formatString) {
+                this._overrideFormatString = true;
+            }
+
+            // Doing complete autoscaling
+            if (this.min == null || this.max == null && this.tickInterval == null && !this.autoscale) {
+                // Check if user must have tick at 0 or 100 and ensure they are in range.
+                // The autoscaling algorithm will always place ticks at 0 and 100 if they are in range.
+                if (this.forceTickAt0) {
+                    if (min > 0) {
+                        min = 0;
+                    }
+                    if (max < 0) {
+                        max = 0;
+                    }
+                }
+
+                if (this.forceTickAt100) {
+                    if (min > 100) {
+                        min = 100;
+                    }
+                    if (max < 100) {
+                        max = 100;
+                    }
+                }
+
+                var keepMin = false,
+                    keepMax = false;
+
+                if (this.min != null) {
+                    keepMin = true;
+                }
+
+                else if (this.max != null) {
+                    keepMax = true;
+                }
+
+                // var threshold = 30;
+                // var tdim = Math.max(dim, threshold+1);
+                // this._scalefact =  (tdim-threshold)/300.0;
+                var ret = $.jqplot.LinearTickGenerator(min, max, this._scalefact, _numberTicks, keepMin, keepMax); 
+                // calculate a padded max and min, points should be less than these
+                // so that they aren't too close to the edges of the plot.
+                // User can adjust how much padding is allowed with pad, padMin and PadMax options. 
+                // If min or max is set, don't pad that end of axis.
+                var tumin = (this.min != null) ? min : min + range*(this.padMin - 1);
+                var tumax = (this.max != null) ? max : max - range*(this.padMax - 1);
+
+                // if they're equal, we shouldn't have to do anything, right?
+                // if (min <=tumin || max >= tumax) {
+                if (min <tumin || max > tumax) {
+                    tumin = (this.min != null) ? min : min - range*(this.padMin - 1);
+                    tumax = (this.max != null) ? max : max + range*(this.padMax - 1);
+                    ret = $.jqplot.LinearTickGenerator(tumin, tumax, this._scalefact, _numberTicks, keepMin, keepMax);
+                }
+
+                this.min = ret[0];
+                this.max = ret[1];
+                // if numberTicks specified, it should return the same.
+                this.numberTicks = ret[2];
+                this._autoFormatString = ret[3];
+                this.tickInterval = ret[4];
+            }
+
+            // User has specified some axis scale related option, can use auto algorithm
+            else {
+                
+                // if min and max are same, space them out a bit
+                if (min == max) {
+                    var adj = 0.05;
+                    if (min > 0) {
+                        adj = Math.max(Math.log(min)/Math.LN10, 0.05);
+                    }
+                    min -= adj;
+                    max += adj;
+                }
+                
+                // autoscale.  Can't autoscale if min or max is supplied.
+                // Will use numberTicks and tickInterval if supplied.  Ticks
+                // across multiple axes may not line up depending on how
+                // bars are to be plotted.
+                if (this.autoscale && this.min == null && this.max == null) {
+                    var rrange, ti, margin;
+                    var forceMinZero = false;
+                    var forceZeroLine = false;
+                    var intervals = {min:null, max:null, average:null, stddev:null};
+                    // if any series are bars, or if any are fill to zero, and if this
+                    // is the axis to fill toward, check to see if we can start axis at zero.
+                    for (var i=0; i<this._series.length; i++) {
+                        var s = this._series[i];
+                        var faname = (s.fillAxis == 'x') ? s._xaxis.name : s._yaxis.name;
+                        // check to see if this is the fill axis
+                        if (this.name == faname) {
+                            var vals = s._plotValues[s.fillAxis];
+                            var vmin = vals[0];
+                            var vmax = vals[0];
+                            for (var j=1; j<vals.length; j++) {
+                                if (vals[j] < vmin) {
+                                    vmin = vals[j];
+                                }
+                                else if (vals[j] > vmax) {
+                                    vmax = vals[j];
+                                }
+                            }
+                            var dp = (vmax - vmin) / vmax;
+                            // is this sries a bar?
+                            if (s.renderer.constructor == $.jqplot.BarRenderer) {
+                                // if no negative values and could also check range.
+                                if (vmin >= 0 && (s.fillToZero || dp > 0.1)) {
+                                    forceMinZero = true;
+                                }
+                                else {
+                                    forceMinZero = false;
+                                    if (s.fill && s.fillToZero && vmin < 0 && vmax > 0) {
+                                        forceZeroLine = true;
+                                    }
+                                    else {
+                                        forceZeroLine = false;
+                                    }
+                                }
+                            }
+                            
+                            // if not a bar and filling, use appropriate method.
+                            else if (s.fill) {
+                                if (vmin >= 0 && (s.fillToZero || dp > 0.1)) {
+                                    forceMinZero = true;
+                                }
+                                else if (vmin < 0 && vmax > 0 && s.fillToZero) {
+                                    forceMinZero = false;
+                                    forceZeroLine = true;
+                                }
+                                else {
+                                    forceMinZero = false;
+                                    forceZeroLine = false;
+                                }
+                            }
+                            
+                            // if not a bar and not filling, only change existing state
+                            // if it doesn't make sense
+                            else if (vmin < 0) {
+                                forceMinZero = false;
+                            }
+                        }
+                    }
+                    
+                    // check if we need make axis min at 0.
+                    if (forceMinZero) {
+                        // compute number of ticks
+                        this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
+                        this.min = 0;
+                        userMin = 0;
+                        // what order is this range?
+                        // what tick interval does that give us?
+                        ti = max/(this.numberTicks-1);
+                        temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
+                        if (ti/temp == parseInt(ti/temp, 10)) {
+                            ti += temp;
+                        }
+                        this.tickInterval = Math.ceil(ti/temp) * temp;
+                        this.max = this.tickInterval * (this.numberTicks - 1);
+                    }
+                    
+                    // check if we need to make sure there is a tick at 0.
+                    else if (forceZeroLine) {
+                        // compute number of ticks
+                        this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
+                        var ntmin = Math.ceil(Math.abs(min)/range*(this.numberTicks-1));
+                        var ntmax = this.numberTicks - 1  - ntmin;
+                        ti = Math.max(Math.abs(min/ntmin), Math.abs(max/ntmax));
+                        temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
+                        this.tickInterval = Math.ceil(ti/temp) * temp;
+                        this.max = this.tickInterval * ntmax;
+                        this.min = -this.tickInterval * ntmin;
+                    }
+                    
+                    // if nothing else, do autoscaling which will try to line up ticks across axes.
+                    else {  
+                        if (this.numberTicks == null){
+                            if (this.tickInterval) {
+                                this.numberTicks = 3 + Math.ceil(range / this.tickInterval);
+                            }
+                            else {
+                                this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
+                            }
+                        }
+                
+                        if (this.tickInterval == null) {
+                            // get a tick interval
+                            ti = range/(this.numberTicks - 1);
+
+                            if (ti < 1) {
+                                temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
+                            }
+                            else {
+                                temp = 1;
+                            }
+                            this.tickInterval = Math.ceil(ti*temp*this.pad)/temp;
+                        }
+                        else {
+                            temp = 1 / this.tickInterval;
+                        }
+                        
+                        // try to compute a nicer, more even tick interval
+                        // temp = Math.pow(10, Math.floor(Math.log(ti)/Math.LN10));
+                        // this.tickInterval = Math.ceil(ti/temp) * temp;
+                        rrange = this.tickInterval * (this.numberTicks - 1);
+                        margin = (rrange - range)/2;
+           
+                        if (this.min == null) {
+                            this.min = Math.floor(temp*(min-margin))/temp;
+                        }
+                        if (this.max == null) {
+                            this.max = this.min + rrange;
+                        }
+                    }
+
+                    // Compute a somewhat decent format string if it is needed.
+                    // get precision of interval and determine a format string.
+                    var sf = $.jqplot.getSignificantFigures(this.tickInterval);
+
+                    var fstr;
+
+                    // if we have only a whole number, use integer formatting
+                    if (sf.digitsLeft >= sf.significantDigits) {
+                        fstr = '%d';
+                    }
+
+                    else {
+                        var temp = Math.max(0, 5 - sf.digitsLeft);
+                        temp = Math.min(temp, sf.digitsRight);
+                        fstr = '%.'+ temp + 'f';
+                    }
+
+                    this._autoFormatString = fstr;
+                }
+                
+                // Use the default algorithm which pads each axis to make the chart
+                // centered nicely on the grid.
+                else {
+
+                    rmin = (this.min != null) ? this.min : min - range*(this.padMin - 1);
+                    rmax = (this.max != null) ? this.max : max + range*(this.padMax - 1);
+                    range = rmax - rmin;
+        
+                    if (this.numberTicks == null){
+                        // if tickInterval is specified by user, we will ignore computed maximum.
+                        // max will be equal or greater to fit even # of ticks.
+                        if (this.tickInterval != null) {
+                            this.numberTicks = Math.ceil((rmax - rmin)/this.tickInterval)+1;
+                        }
+                        else if (dim > 100) {
+                            this.numberTicks = parseInt(3+(dim-100)/75, 10);
+                        }
+                        else {
+                            this.numberTicks = 2;
+                        }
+                    }
+                
+                    if (this.tickInterval == null) {
+                        this.tickInterval = range / (this.numberTicks-1);
+                    }
+                    
+                    if (this.max == null) {
+                        rmax = rmin + this.tickInterval*(this.numberTicks - 1);
+                    }        
+                    if (this.min == null) {
+                        rmin = rmax - this.tickInterval*(this.numberTicks - 1);
+                    }
+
+                    // get precision of interval and determine a format string.
+                    var sf = $.jqplot.getSignificantFigures(this.tickInterval);
+
+                    var fstr;
+
+                    // if we have only a whole number, use integer formatting
+                    if (sf.digitsLeft >= sf.significantDigits) {
+                        fstr = '%d';
+                    }
+
+                    else {
+                        var temp = Math.max(0, 5 - sf.digitsLeft);
+                        temp = Math.min(temp, sf.digitsRight);
+                        fstr = '%.'+ temp + 'f';
+                    }
+
+
+                    this._autoFormatString = fstr;
+
+                    this.min = rmin;
+                    this.max = rmax;
+                }
+                
+                if (this.renderer.constructor == $.jqplot.LinearAxisRenderer && this._autoFormatString == '') {
+                    // fix for misleading tick display with small range and low precision.
+                    range = this.max - this.min;
+                    // figure out precision
+                    var temptick = new this.tickRenderer(this.tickOptions);
+                    // use the tick formatString or, the default.
+                    var fs = temptick.formatString || $.jqplot.config.defaultTickFormatString; 
+                    var fs = fs.match($.jqplot.sprintf.regex)[0];
+                    var precision = 0;
+                    if (fs) {
+                        if (fs.search(/[fFeEgGpP]/) > -1) {
+                            var m = fs.match(/\%\.(\d{0,})?[eEfFgGpP]/);
+                            if (m) {
+                                precision = parseInt(m[1], 10);
+                            }
+                            else {
+                                precision = 6;
+                            }
+                        }
+                        else if (fs.search(/[di]/) > -1) {
+                            precision = 0;
+                        }
+                        // fact will be <= 1;
+                        var fact = Math.pow(10, -precision);
+                        if (this.tickInterval < fact) {
+                            // need to correct underrange
+                            if (userNT == null && userTI == null) {
+                                this.tickInterval = fact;
+                                if (userMax == null && userMin == null) {
+                                    // this.min = Math.floor((this._dataBounds.min - this.tickInterval)/fact) * fact;
+                                    this.min = Math.floor(this._dataBounds.min/fact) * fact;
+                                    if (this.min == this._dataBounds.min) {
+                                        this.min = this._dataBounds.min - this.tickInterval;
+                                    }
+                                    // this.max = Math.ceil((this._dataBounds.max + this.tickInterval)/fact) * fact;
+                                    this.max = Math.ceil(this._dataBounds.max/fact) * fact;
+                                    if (this.max == this._dataBounds.max) {
+                                        this.max = this._dataBounds.max + this.tickInterval;
+                                    }
+                                    var n = (this.max - this.min)/this.tickInterval;
+                                    n = n.toFixed(11);
+                                    n = Math.ceil(n);
+                                    this.numberTicks = n + 1;
+                                }
+                                else if (userMax == null) {
+                                    // add one tick for top of range.
+                                    var n = (this._dataBounds.max - this.min) / this.tickInterval;
+                                    n = n.toFixed(11);
+                                    this.numberTicks = Math.ceil(n) + 2;
+                                    this.max = this.min + this.tickInterval * (this.numberTicks-1);
+                                }
+                                else if (userMin == null) {
+                                    // add one tick for bottom of range.
+                                    var n = (this.max - this._dataBounds.min) / this.tickInterval;
+                                    n = n.toFixed(11);
+                                    this.numberTicks = Math.ceil(n) + 2;
+                                    this.min = this.max - this.tickInterval * (this.numberTicks-1);
+                                }
+                                else {
+                                    // calculate a number of ticks so max is within axis scale
+                                    this.numberTicks = Math.ceil((userMax - userMin)/this.tickInterval) + 1;
+                                    // if user's min and max don't fit evenly in ticks, adjust.
+                                    // This takes care of cases such as user min set to 0, max set to 3.5 but tick
+                                    // format string set to %d (integer ticks)
+                                    this.min =  Math.floor(userMin*Math.pow(10, precision))/Math.pow(10, precision);
+                                    this.max =  Math.ceil(userMax*Math.pow(10, precision))/Math.pow(10, precision);
+                                    // this.max = this.min + this.tickInterval*(this.numberTicks-1);
+                                    this.numberTicks = Math.ceil((this.max - this.min)/this.tickInterval) + 1;
+                                }
+                            }
+                        }
+                    }
+                }
+                
+            }
+            
+            if (this._overrideFormatString && this._autoFormatString != '') {
+                this.tickOptions = this.tickOptions || {};
+                this.tickOptions.formatString = this._autoFormatString;
+            }
+
+            var t, to;
+            for (var i=0; i<this.numberTicks; i++){
+                tt = this.min + i * this.tickInterval;
+                t = new this.tickRenderer(this.tickOptions);
+                // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
+
+                t.setTick(tt, this.name);
+                this._ticks.push(t);
+
+                if (i < this.numberTicks - 1) {
+                    for (var j=0; j<this.minorTicks; j++) {
+                        tt += this.tickInterval/(this.minorTicks+1);
+                        to = $.extend(true, {}, this.tickOptions, {name:this.name, value:tt, label:'', isMinorTick:true});
+                        t = new this.tickRenderer(to);
+                        this._ticks.push(t);
+                    }
+                }
+                t = null;
+            }
+        }
+
+        if (this.tickInset) {
+            this.min = this.min - this.tickInset * this.tickInterval;
+            this.max = this.max + this.tickInset * this.tickInterval;
+        }
+
+        ticks = null;
+    };
+    
+    // Used to reset just the values of the ticks and then repack, which will
+    // recalculate the positioning functions.  It is assuemd that the 
+    // number of ticks is the same and the values of the new array are at the
+    // proper interval.
+    // This method needs to be called with the scope of an axis object, like:
+    //
+    // > plot.axes.yaxis.renderer.resetTickValues.call(plot.axes.yaxis, yarr);
+    //
+    $.jqplot.LinearAxisRenderer.prototype.resetTickValues = function(opts) {
+        if ($.isArray(opts) && opts.length == this._ticks.length) {
+            var t;
+            for (var i=0; i<opts.length; i++) {
+                t = this._ticks[i];
+                t.value = opts[i];
+                t.label = t.formatter(t.formatString, opts[i]);
+                t.label = t.prefix + t.label;
+                t._elem.html(t.label);
+            }
+            t = null;
+            this.min = $.jqplot.arrayMin(opts);
+            this.max = $.jqplot.arrayMax(opts);
+            this.pack();
+        }
+        // Not implemented yet.
+        // else if ($.isPlainObject(opts)) {
+        // 
+        // }
+    };
+    
+    // called with scope of axis
+    $.jqplot.LinearAxisRenderer.prototype.pack = function(pos, offsets) {
+        // Add defaults for repacking from resetTickValues function.
+        pos = pos || {};
+        offsets = offsets || this._offsets;
+        
+        var ticks = this._ticks;
+        var max = this.max;
+        var min = this.min;
+        var offmax = offsets.max;
+        var offmin = offsets.min;
+        var lshow = (this._label == null) ? false : this._label.show;
+        
+        for (var p in pos) {
+            this._elem.css(p, pos[p]);
+        }
+        
+        this._offsets = offsets;
+        // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
+        var pixellength = offmax - offmin;
+        var unitlength = max - min;
+        
+        // point to unit and unit to point conversions references to Plot DOM element top left corner.
+        if (this.breakPoints) {
+            unitlength = unitlength - this.breakPoints[1] + this.breakPoints[0];
+            
+            this.p2u = function(p){
+                return (p - offmin) * unitlength / pixellength + min;
+            };
+        
+            this.u2p = function(u){
+                if (u > this.breakPoints[0] && u < this.breakPoints[1]){
+                    u = this.breakPoints[0];
+                }
+                if (u <= this.breakPoints[0]) {
+                    return (u - min) * pixellength / unitlength + offmin;
+                }
+                else {
+                    return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength + offmin;
+                }
+            };
+                
+            if (this.name.charAt(0) == 'x'){
+                this.series_u2p = function(u){
+                    if (u > this.breakPoints[0] && u < this.breakPoints[1]){
+                        u = this.breakPoints[0];
+                    }
+                    if (u <= this.breakPoints[0]) {
+                        return (u - min) * pixellength / unitlength;
+                    }
+                    else {
+                        return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength;
+                    }
+                };
+                this.series_p2u = function(p){
+                    return p * unitlength / pixellength + min;
+                };
+            }
+        
+            else {
+                this.series_u2p = function(u){
+                    if (u > this.breakPoints[0] && u < this.breakPoints[1]){
+                        u = this.breakPoints[0];
+                    }
+                    if (u >= this.breakPoints[1]) {
+                        return (u - max) * pixellength / unitlength;
+                    }
+                    else {
+                        return (u + this.breakPoints[1] - this.breakPoints[0] - max) * pixellength / unitlength;
+                    }
+                };
+                this.series_p2u = function(p){
+                    return p * unitlength / pixellength + max;
+                };
+            }
+        }
+        else {
+            this.p2u = function(p){
+                return (p - offmin) * unitlength / pixellength + min;
+            };
+        
+            this.u2p = function(u){
+                return (u - min) * pixellength / unitlength + offmin;
+            };
+                
+            if (this.name == 'xaxis' || this.name == 'x2axis'){
+                this.series_u2p = function(u){
+                    return (u - min) * pixellength / unitlength;
+                };
+                this.series_p2u = function(p){
+                    return p * unitlength / pixellength + min;
+                };
+            }
+        
+            else {
+                this.series_u2p = function(u){
+                    return (u - max) * pixellength / unitlength;
+                };
+                this.series_p2u = function(p){
+                    return p * unitlength / pixellength + max;
+                };
+            }
+        }
+        
+        if (this.show) {
+            if (this.name == 'xaxis' || this.name == 'x2axis') {
+                for (var i=0; i<ticks.length; i++) {
+                    var t = ticks[i];
+                    if (t.show && t.showLabel) {
+                        var shim;
+                        
+                        if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+                            // will need to adjust auto positioning based on which axis this is.
+                            var temp = (this.name == 'xaxis') ? 1 : -1;
+                            switch (t.labelPosition) {
+                                case 'auto':
+                                    // position at end
+                                    if (temp * t.angle < 0) {
+                                        shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    }
+                                    // position at start
+                                    else {
+                                        shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'end':
+                                    shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                                case 'start':
+                                    shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    break;
+                                case 'middle':
+                                    shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                                default:
+                                    shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                            }
+                        }
+                        else {
+                            shim = -t.getWidth()/2;
+                        }
+                        var val = this.u2p(t.value) + shim + 'px';
+                        t._elem.css('left', val);
+                        t.pack();
+                    }
+                }
+                if (lshow) {
+                    var w = this._label._elem.outerWidth(true);
+                    this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
+                    if (this.name == 'xaxis') {
+                        this._label._elem.css('bottom', '0px');
+                    }
+                    else {
+                        this._label._elem.css('top', '0px');
+                    }
+                    this._label.pack();
+                }
+            }
+            else {
+                for (var i=0; i<ticks.length; i++) {
+                    var t = ticks[i];
+                    if (t.show && t.showLabel) {                        
+                        var shim;
+                        if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+                            // will need to adjust auto positioning based on which axis this is.
+                            var temp = (this.name == 'yaxis') ? 1 : -1;
+                            switch (t.labelPosition) {
+                                case 'auto':
+                                    // position at end
+                                case 'end':
+                                    if (temp * t.angle < 0) {
+                                        shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+                                    }
+                                    else {
+                                        shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'start':
+                                    if (t.angle > 0) {
+                                        shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+                                    }
+                                    else {
+                                        shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'middle':
+                                    // if (t.angle > 0) {
+                                    //     shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    // }
+                                    // else {
+                                    //     shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    // }
+                                    shim = -t.getHeight()/2;
+                                    break;
+                                default:
+                                    shim = -t.getHeight()/2;
+                                    break;
+                            }
+                        }
+                        else {
+                            shim = -t.getHeight()/2;
+                        }
+                        
+                        var val = this.u2p(t.value) + shim + 'px';
+                        t._elem.css('top', val);
+                        t.pack();
+                    }
+                }
+                if (lshow) {
+                    var h = this._label._elem.outerHeight(true);
+                    this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
+                    if (this.name == 'yaxis') {
+                        this._label._elem.css('left', '0px');
+                    }
+                    else {
+                        this._label._elem.css('right', '0px');
+                    }   
+                    this._label.pack();
+                }
+            }
+        }
+
+        ticks = null;
+    };
+
+
+    /**
+    * The following code was generaously given to me a while back by Scott Prahl.
+    * He did a good job at computing axes min, max and number of ticks for the 
+    * case where the user has not set any scale related parameters (tickInterval,
+    * numberTicks, min or max).  I had ignored this use case for a long time,
+    * focusing on the more difficult case where user has set some option controlling
+    * tick generation.  Anyway, about time I got this into jqPlot.
+    * Thanks Scott!!
+    */
+    
+    /**
+    * Copyright (c) 2010 Scott Prahl
+    * The next three routines are currently available for use in all personal 
+    * or commercial projects under both the MIT and GPL version 2.0 licenses. 
+    * This means that you can choose the license that best suits your project 
+    * and use it accordingly. 
+    */
+
+    // A good format string depends on the interval. If the interval is greater 
+    // than 1 then there is no need to show any decimal digits. If it is < 1.0, then
+    // use the magnitude of the interval to determine the number of digits to show.
+    function bestFormatString (interval)
+    {
+        var fstr;
+        interval = Math.abs(interval);
+        if (interval >= 10) {
+            fstr = '%d';
+        }
+
+        else if (interval > 1) {
+            if (interval === parseInt(interval, 10)) {
+                fstr = '%d';
+            }
+            else {
+                fstr = '%.1f';
+            }
+        }
+
+        else {
+            var expv = -Math.floor(Math.log(interval)/Math.LN10);
+            fstr = '%.' + expv + 'f';
+        }
+        
+        return fstr; 
+    }
+
+    var _factors = [0.1, 0.2, 0.3, 0.4, 0.5, 0.8, 1, 2, 3, 4, 5];
+
+    var _getLowerFactor = function(f) {
+        var i = _factors.indexOf(f);
+        if (i > 0) {
+            return _factors[i-1];
+        }
+        else {
+            return _factors[_factors.length - 1] / 100;
+        }
+    };
+
+    var _getHigherFactor = function(f) {
+        var i = _factors.indexOf(f);
+        if (i < _factors.length-1) {
+            return _factors[i+1];
+        }
+        else {
+            return _factors[0] * 100;
+        }
+    };
+
+    // Given a fixed minimum and maximum and a target number ot ticks
+    // figure out the best interval and 
+    // return min, max, number ticks, format string and tick interval
+    function bestConstrainedInterval(min, max, nttarget) {
+        // run through possible number to ticks and see which interval is best
+        var low = Math.floor(nttarget/2);
+        var hi = Math.ceil(nttarget*1.5);
+        var badness = Number.MAX_VALUE;
+        var r = (max - min);
+        var temp;
+        var sd;
+        var bestNT;
+        var gsf = $.jqplot.getSignificantFigures;
+        var fsd;
+        var fs;
+        var currentNT;
+        var bestPrec;
+
+        for (var i=0, l=hi-low+1; i<l; i++) {
+            currentNT = low + i;
+            temp = r/(currentNT-1);
+            sd = gsf(temp);
+
+            temp = Math.abs(nttarget - currentNT) + sd.digitsRight;
+            if (temp < badness) {
+                badness = temp;
+                bestNT = currentNT;
+                bestPrec = sd.digitsRight;
+            }
+            else if (temp === badness) {
+                // let nicer ticks trump number ot ticks
+                if (sd.digitsRight < bestPrec) {
+                    bestNT = currentNT;
+                    bestPrec = sd.digitsRight;
+                }
+            }
+
+        }
+
+        fsd = Math.max(bestPrec, Math.max(gsf(min).digitsRight, gsf(max).digitsRight));
+        if (fsd === 0) {
+            fs = '%d';
+        }
+        else {
+            fs = '%.' + fsd + 'f';
+        }
+        temp = r / (bestNT - 1);
+        // min, max, number ticks, format string, tick interval
+        return [min, max, bestNT, fs, temp];
+    }
+
+    // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
+    // it is based soley on the range and number of ticks.  So if user specifies
+    // number of ticks, use this.
+    function bestInterval(range, numberTicks) {
+        numberTicks = numberTicks || 7;
+        var minimum = range / (numberTicks - 1);
+        var magnitude = Math.pow(10, Math.floor(Math.log(minimum) / Math.LN10));
+        var residual = minimum / magnitude;
+        var interval;
+        // "nicest" ranges are 1, 2, 5 or powers of these.
+        // for magnitudes below 1, only allow these. 
+        if (magnitude < 1) {
+            if (residual > 5) {
+                interval = 10 * magnitude;
+            }
+            else if (residual > 2) {
+                interval = 5 * magnitude;
+            }
+            else if (residual > 1) {
+                interval = 2 * magnitude;
+            }
+            else {
+                interval = magnitude;
+            }
+        }
+        // for large ranges (whole integers), allow intervals like 3, 4 or powers of these.
+        // this helps a lot with poor choices for number of ticks. 
+        else {
+            if (residual > 5) {
+                interval = 10 * magnitude;
+            }
+            else if (residual > 4) {
+                interval = 5 * magnitude;
+            }
+            else if (residual > 3) {
+                interval = 4 * magnitude;
+            }
+            else if (residual > 2) {
+                interval = 3 * magnitude;
+            }
+            else if (residual > 1) {
+                interval = 2 * magnitude;
+            }
+            else {
+                interval = magnitude;
+            }
+        }
+
+        return interval;
+    }
+
+    // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
+    // it is based soley on the range of data, number of ticks must be computed later.
+    function bestLinearInterval(range, scalefact) {
+        scalefact = scalefact || 1;
+        var expv = Math.floor(Math.log(range)/Math.LN10);
+        var magnitude = Math.pow(10, expv);
+        // 0 < f < 10
+        var f = range / magnitude;
+        var fact;
+        // for large plots, scalefact will decrease f and increase number of ticks.
+        // for small plots, scalefact will increase f and decrease number of ticks.
+        f = f/scalefact;
+
+        // for large plots, smaller interval, more ticks.
+        if (f<=0.38) {
+            fact = 0.1;
+        }
+        else if (f<=1.6) {
+            fact = 0.2;
+        }
+        else if (f<=4.0) {
+            fact = 0.5;
+        }
+        else if (f<=8.0) {
+            fact = 1.0;
+        }
+        // for very small plots, larger interval, less ticks in number ticks
+        else if (f<=16.0) {
+            fact = 2;
+        }
+        else {
+            fact = 5;
+        } 
+
+        return fact*magnitude; 
+    }
+
+    function bestLinearComponents(range, scalefact) {
+        var expv = Math.floor(Math.log(range)/Math.LN10);
+        var magnitude = Math.pow(10, expv);
+        // 0 < f < 10
+        var f = range / magnitude;
+        var interval;
+        var fact;
+        // for large plots, scalefact will decrease f and increase number of ticks.
+        // for small plots, scalefact will increase f and decrease number of ticks.
+        f = f/scalefact;
+
+        // for large plots, smaller interval, more ticks.
+        if (f<=0.38) {
+            fact = 0.1;
+        }
+        else if (f<=1.6) {
+            fact = 0.2;
+        }
+        else if (f<=4.0) {
+            fact = 0.5;
+        }
+        else if (f<=8.0) {
+            fact = 1.0;
+        }
+        // for very small plots, larger interval, less ticks in number ticks
+        else if (f<=16.0) {
+            fact = 2;
+        }
+        // else if (f<=20.0) {
+        //     fact = 3;
+        // }
+        // else if (f<=24.0) {
+        //     fact = 4;
+        // }
+        else {
+            fact = 5;
+        } 
+
+        interval = fact * magnitude;
+
+        return [interval, fact, magnitude];
+    }
+
+    // Given the min and max for a dataset, return suitable endpoints
+    // for the graphing, a good number for the number of ticks, and a
+    // format string so that extraneous digits are not displayed.
+    // returned is an array containing [min, max, nTicks, format]
+    $.jqplot.LinearTickGenerator = function(axis_min, axis_max, scalefact, numberTicks, keepMin, keepMax) {
+        // Set to preserve EITHER min OR max.
+        // If min is preserved, max must be free.
+        keepMin = (keepMin === null) ? false : keepMin;
+        keepMax = (keepMax === null || keepMin) ? false : keepMax;
+        // if endpoints are equal try to include zero otherwise include one
+        if (axis_min === axis_max) {
+            axis_max = (axis_max) ? 0 : 1;
+        }
+
+        scalefact = scalefact || 1.0;
+
+        // make sure range is positive
+        if (axis_max < axis_min) {
+            var a = axis_max;
+            axis_max = axis_min;
+            axis_min = a;
+        }
+
+        var r = [];
+        var ss = bestLinearInterval(axis_max - axis_min, scalefact);
+
+        var gsf = $.jqplot.getSignificantFigures;
+        
+        if (numberTicks == null) {
+
+            // Figure out the axis min, max and number of ticks
+            // the min and max will be some multiple of the tick interval,
+            // 1*10^n, 2*10^n or 5*10^n.  This gaurantees that, if the
+            // axis min is negative, 0 will be a tick.
+            if (!keepMin && !keepMax) {
+                r[0] = Math.floor(axis_min / ss) * ss;  // min
+                r[1] = Math.ceil(axis_max / ss) * ss;   // max
+                r[2] = Math.round((r[1]-r[0])/ss+1.0);  // number of ticks
+                r[3] = bestFormatString(ss);            // format string
+                r[4] = ss;                              // tick Interval
+            }
+
+            else if (keepMin) {
+                r[0] = axis_min;                                        // min
+                r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0);     // number of ticks
+                r[1] = axis_min + (r[2] - 1) * ss;                      // max
+                var digitsMin = gsf(axis_min).digitsRight;
+                var digitsSS = gsf(ss).digitsRight;
+                if (digitsMin < digitsSS) {
+                    r[3] = bestFormatString(ss);                        // format string
+                }
+                else {
+                    r[3] = '%.' + digitsMin + 'f';
+                }
+                r[4] = ss;                                              // tick Interval
+            }
+
+            else if (keepMax) {
+                r[1] = axis_max;                                        // max
+                r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0);     // number of ticks
+                r[0] = axis_max - (r[2] - 1) * ss;                      // min
+                var digitsMax = gsf(axis_max).digitsRight;
+                var digitsSS = gsf(ss).digitsRight;
+                if (digitsMax < digitsSS) {
+                    r[3] = bestFormatString(ss);                        // format string
+                }
+                else {
+                    r[3] = '%.' + digitsMax + 'f';
+                }
+                r[4] = ss;                                              // tick Interval
+            }
+        }
+
+        else {
+            var tempr = [];
+
+            // Figure out the axis min, max and number of ticks
+            // the min and max will be some multiple of the tick interval,
+            // 1*10^n, 2*10^n or 5*10^n.  This gaurantees that, if the
+            // axis min is negative, 0 will be a tick.
+            tempr[0] = Math.floor(axis_min / ss) * ss;  // min
+            tempr[1] = Math.ceil(axis_max / ss) * ss;   // max
+            tempr[2] = Math.round((tempr[1]-tempr[0])/ss+1.0);    // number of ticks
+            tempr[3] = bestFormatString(ss);            // format string
+            tempr[4] = ss;                              // tick Interval
+
+            // first, see if we happen to get the right number of ticks
+            if (tempr[2] === numberTicks) {
+                r = tempr;
+            }
+
+            else {
+
+                var newti = bestInterval(tempr[1] - tempr[0], numberTicks);
+
+                r[0] = tempr[0];                        // min
+                r[2] = numberTicks;                     // number of ticks
+                r[4] = newti;                           // tick interval
+                r[3] = bestFormatString(newti);         // format string
+                r[1] = r[0] + (r[2] - 1) * r[4];        // max
+            }
+        }
+
+        return r;
+    };
+
+    $.jqplot.LinearTickGenerator.bestLinearInterval = bestLinearInterval;
+    $.jqplot.LinearTickGenerator.bestInterval = bestInterval;
+    $.jqplot.LinearTickGenerator.bestLinearComponents = bestLinearComponents;
+    $.jqplot.LinearTickGenerator.bestConstrainedInterval = bestConstrainedInterval;
+
+
+    // class: $.jqplot.MarkerRenderer
+    // The default jqPlot marker renderer, rendering the points on the line.
+    $.jqplot.MarkerRenderer = function(options){
+        // Group: Properties
+        
+        // prop: show
+        // whether or not to show the marker.
+        this.show = true;
+        // prop: style
+        // One of diamond, circle, square, x, plus, dash, filledDiamond, filledCircle, filledSquare
+        this.style = 'filledCircle';
+        // prop: lineWidth
+        // size of the line for non-filled markers.
+        this.lineWidth = 2;
+        // prop: size
+        // Size of the marker (diameter or circle, length of edge of square, etc.)
+        this.size = 9.0;
+        // prop: color
+        // color of marker.  Will be set to color of series by default on init.
+        this.color = '#666666';
+        // prop: shadow
+        // whether or not to draw a shadow on the line
+        this.shadow = true;
+        // prop: shadowAngle
+        // Shadow angle in degrees
+        this.shadowAngle = 45;
+        // prop: shadowOffset
+        // Shadow offset from line in pixels
+        this.shadowOffset = 1;
+        // prop: shadowDepth
+        // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
+        this.shadowDepth = 3;
+        // prop: shadowAlpha
+        // Alpha channel transparency of shadow.  0 = transparent.
+        this.shadowAlpha = '0.07';
+        // prop: shadowRenderer
+        // Renderer that will draws the shadows on the marker.
+        this.shadowRenderer = new $.jqplot.ShadowRenderer();
+        // prop: shapeRenderer
+        // Renderer that will draw the marker.
+        this.shapeRenderer = new $.jqplot.ShapeRenderer();
+        
+        $.extend(true, this, options);
+    };
+    
+    $.jqplot.MarkerRenderer.prototype.init = function(options) {
+        $.extend(true, this, options);
+        var sdopt = {angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, lineWidth:this.lineWidth, depth:this.shadowDepth, closePath:true};
+        if (this.style.indexOf('filled') != -1) {
+            sdopt.fill = true;
+        }
+        if (this.style.indexOf('ircle') != -1) {
+            sdopt.isarc = true;
+            sdopt.closePath = false;
+        }
+        this.shadowRenderer.init(sdopt);
+        
+        var shopt = {fill:false, isarc:false, strokeStyle:this.color, fillStyle:this.color, lineWidth:this.lineWidth, closePath:true};
+        if (this.style.indexOf('filled') != -1) {
+            shopt.fill = true;
+        }
+        if (this.style.indexOf('ircle') != -1) {
+            shopt.isarc = true;
+            shopt.closePath = false;
+        }
+        this.shapeRenderer.init(shopt);
+    };
+    
+    $.jqplot.MarkerRenderer.prototype.drawDiamond = function(x, y, ctx, fill, options) {
+        var stretch = 1.2;
+        var dx = this.size/2/stretch;
+        var dy = this.size/2*stretch;
+        var points = [[x-dx, y], [x, y+dy], [x+dx, y], [x, y-dy]];
+        if (this.shadow) {
+            this.shadowRenderer.draw(ctx, points);
+        }
+        this.shapeRenderer.draw(ctx, points, options);
+    };
+    
+    $.jqplot.MarkerRenderer.prototype.drawPlus = function(x, y, ctx, fill, options) {
+        var stretch = 1.0;
+        var dx = this.size/2*stretch;
+        var dy = this.size/2*stretch;
+        var points1 = [[x, y-dy], [x, y+dy]];
+        var points2 = [[x+dx, y], [x-dx, y]];
+        var opts = $.extend(true, {}, this.options, {closePath:false});
+        if (this.shadow) {
+            this.shadowRenderer.draw(ctx, points1, {closePath:false});
+            this.shadowRenderer.draw(ctx, points2, {closePath:false});
+        }
+        this.shapeRenderer.draw(ctx, points1, opts);
+        this.shapeRenderer.draw(ctx, points2, opts);
+    };
+    
+    $.jqplot.MarkerRenderer.prototype.drawX = function(x, y, ctx, fill, options) {
+        var stretch = 1.0;
+        var dx = this.size/2*stretch;
+        var dy = this.size/2*stretch;
+        var opts = $.extend(true, {}, this.options, {closePath:false});
+        var points1 = [[x-dx, y-dy], [x+dx, y+dy]];
+        var points2 = [[x-dx, y+dy], [x+dx, y-dy]];
+        if (this.shadow) {
+            this.shadowRenderer.draw(ctx, points1, {closePath:false});
+            this.shadowRenderer.draw(ctx, points2, {closePath:false});
+        }
+        this.shapeRenderer.draw(ctx, points1, opts);
+        this.shapeRenderer.draw(ctx, points2, opts);
+    };
+    
+    $.jqplot.MarkerRenderer.prototype.drawDash = function(x, y, ctx, fill, options) {
+        var stretch = 1.0;
+        var dx = this.size/2*stretch;
+        var dy = this.size/2*stretch;
+        var points = [[x-dx, y], [x+dx, y]];
+        if (this.shadow) {
+            this.shadowRenderer.draw(ctx, points);
+        }
+        this.shapeRenderer.draw(ctx, points, options);
+    };
+    
+    $.jqplot.MarkerRenderer.prototype.drawLine = function(p1, p2, ctx, fill, options) {
+        var points = [p1, p2];
+        if (this.shadow) {
+            this.shadowRenderer.draw(ctx, points);
+        }
+        this.shapeRenderer.draw(ctx, points, options);
+    };
+    
+    $.jqplot.MarkerRenderer.prototype.drawSquare = function(x, y, ctx, fill, options) {
+        var stretch = 1.0;
+        var dx = this.size/2/stretch;
+        var dy = this.size/2*stretch;
+        var points = [[x-dx, y-dy], [x-dx, y+dy], [x+dx, y+dy], [x+dx, y-dy]];
+        if (this.shadow) {
+            this.shadowRenderer.draw(ctx, points);
+        }
+        this.shapeRenderer.draw(ctx, points, options);
+    };
+    
+    $.jqplot.MarkerRenderer.prototype.drawCircle = function(x, y, ctx, fill, options) {
+        var radius = this.size/2;
+        var end = 2*Math.PI;
+        var points = [x, y, radius, 0, end, true];
+        if (this.shadow) {
+            this.shadowRenderer.draw(ctx, points);
+        }
+        this.shapeRenderer.draw(ctx, points, options);
+    };
+    
+    $.jqplot.MarkerRenderer.prototype.draw = function(x, y, ctx, options) {
+        options = options || {};
+        // hack here b/c shape renderer uses canvas based color style options
+        // and marker uses css style names.
+        if (options.show == null || options.show != false) {
+            if (options.color && !options.fillStyle) {
+                options.fillStyle = options.color;
+            }
+            if (options.color && !options.strokeStyle) {
+                options.strokeStyle = options.color;
+            }
+            switch (this.style) {
+                case 'diamond':
+                    this.drawDiamond(x,y,ctx, false, options);
+                    break;
+                case 'filledDiamond':
+                    this.drawDiamond(x,y,ctx, true, options);
+                    break;
+                case 'circle':
+                    this.drawCircle(x,y,ctx, false, options);
+                    break;
+                case 'filledCircle':
+                    this.drawCircle(x,y,ctx, true, options);
+                    break;
+                case 'square':
+                    this.drawSquare(x,y,ctx, false, options);
+                    break;
+                case 'filledSquare':
+                    this.drawSquare(x,y,ctx, true, options);
+                    break;
+                case 'x':
+                    this.drawX(x,y,ctx, true, options);
+                    break;
+                case 'plus':
+                    this.drawPlus(x,y,ctx, true, options);
+                    break;
+                case 'dash':
+                    this.drawDash(x,y,ctx, true, options);
+                    break;
+                case 'line':
+                    this.drawLine(x, y, ctx, false, options);
+                    break;
+                default:
+                    this.drawDiamond(x,y,ctx, false, options);
+                    break;
+            }
+        }
+    };
+    
+    // class: $.jqplot.shadowRenderer
+    // The default jqPlot shadow renderer, rendering shadows behind shapes.
+    $.jqplot.ShadowRenderer = function(options){ 
+        // Group: Properties
+        
+        // prop: angle
+        // Angle of the shadow in degrees.  Measured counter-clockwise from the x axis.
+        this.angle = 45;
+        // prop: offset
+        // Pixel offset at the given shadow angle of each shadow stroke from the last stroke.
+        this.offset = 1;
+        // prop: alpha
+        // alpha transparency of shadow stroke.
+        this.alpha = 0.07;
+        // prop: lineWidth
+        // width of the shadow line stroke.
+        this.lineWidth = 1.5;
+        // prop: lineJoin
+        // How line segments of the shadow are joined.
+        this.lineJoin = 'miter';
+        // prop: lineCap
+        // how ends of the shadow line are rendered.
+        this.lineCap = 'round';
+        // prop; closePath
+        // whether line path segment is closed upon itself.
+        this.closePath = false;
+        // prop: fill
+        // whether to fill the shape.
+        this.fill = false;
+        // prop: depth
+        // how many times the shadow is stroked.  Each stroke will be offset by offset at angle degrees.
+        this.depth = 3;
+        this.strokeStyle = 'rgba(0,0,0,0.1)';
+        // prop: isarc
+        // whether the shadow is an arc or not.
+        this.isarc = false;
+        
+        $.extend(true, this, options);
+    };
+    
+    $.jqplot.ShadowRenderer.prototype.init = function(options) {
+        $.extend(true, this, options);
+    };
+    
+    // function: draw
+    // draws an transparent black (i.e. gray) shadow.
+    //
+    // ctx - canvas drawing context
+    // points - array of points or [x, y, radius, start angle (rad), end angle (rad)]
+    $.jqplot.ShadowRenderer.prototype.draw = function(ctx, points, options) {
+        ctx.save();
+        var opts = (options != null) ? options : {};
+        var fill = (opts.fill != null) ? opts.fill : this.fill;
+        var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect;
+        var closePath = (opts.closePath != null) ? opts.closePath : this.closePath;
+        var offset = (opts.offset != null) ? opts.offset : this.offset;
+        var alpha = (opts.alpha != null) ? opts.alpha : this.alpha;
+        var depth = (opts.depth != null) ? opts.depth : this.depth;
+        var isarc = (opts.isarc != null) ? opts.isarc : this.isarc;
+        var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern;
+        ctx.lineWidth = (opts.lineWidth != null) ? opts.lineWidth : this.lineWidth;
+        ctx.lineJoin = (opts.lineJoin != null) ? opts.lineJoin : this.lineJoin;
+        ctx.lineCap = (opts.lineCap != null) ? opts.lineCap : this.lineCap;
+        ctx.strokeStyle = opts.strokeStyle || this.strokeStyle || 'rgba(0,0,0,'+alpha+')';
+        ctx.fillStyle = opts.fillStyle || this.fillStyle || 'rgba(0,0,0,'+alpha+')';
+        for (var j=0; j<depth; j++) {
+            var ctxPattern = $.jqplot.LinePattern(ctx, linePattern);
+            ctx.translate(Math.cos(this.angle*Math.PI/180)*offset, Math.sin(this.angle*Math.PI/180)*offset);
+            ctxPattern.beginPath();
+            if (isarc) {
+                ctx.arc(points[0], points[1], points[2], points[3], points[4], true);                
+            }
+            else if (fillRect) {
+                if (fillRect) {
+                    ctx.fillRect(points[0], points[1], points[2], points[3]);
+                }
+            }
+            else if (points && points.length){
+                var move = true;
+                for (var i=0; i<points.length; i++) {
+                    // skip to the first non-null point and move to it.
+                    if (points[i][0] != null && points[i][1] != null) {
+                        if (move) {
+                            ctxPattern.moveTo(points[i][0], points[i][1]);
+                            move = false;
+                        }
+                        else {
+                            ctxPattern.lineTo(points[i][0], points[i][1]);
+                        }
+                    }
+                    else {
+                        move = true;
+                    }
+                }
+                
+            }
+            if (closePath) {
+                ctxPattern.closePath();
+            }
+            if (fill) {
+                ctx.fill();
+            }
+            else {
+                ctx.stroke();
+            }
+        }
+        ctx.restore();
+    };
+    
+    // class: $.jqplot.shapeRenderer
+    // The default jqPlot shape renderer.  Given a set of points will
+    // plot them and either stroke a line (fill = false) or fill them (fill = true).
+    // If a filled shape is desired, closePath = true must also be set to close
+    // the shape.
+    $.jqplot.ShapeRenderer = function(options){
+        
+        this.lineWidth = 1.5;
+        // prop: linePattern
+        // line pattern 'dashed', 'dotted', 'solid', some combination
+        // of '-' and '.' characters such as '.-.' or a numerical array like 
+        // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line, 
+        // [1, 10, 20, 10] to draw a dot-dash line, and so on.
+        this.linePattern = 'solid';
+        // prop: lineJoin
+        // How line segments of the shadow are joined.
+        this.lineJoin = 'miter';
+        // prop: lineCap
+        // how ends of the shadow line are rendered.
+        this.lineCap = 'round';
+        // prop; closePath
+        // whether line path segment is closed upon itself.
+        this.closePath = false;
+        // prop: fill
+        // whether to fill the shape.
+        this.fill = false;
+        // prop: isarc
+        // whether the shadow is an arc or not.
+        this.isarc = false;
+        // prop: fillRect
+        // true to draw shape as a filled rectangle.
+        this.fillRect = false;
+        // prop: strokeRect
+        // true to draw shape as a stroked rectangle.
+        this.strokeRect = false;
+        // prop: clearRect
+        // true to cear a rectangle.
+        this.clearRect = false;
+        // prop: strokeStyle
+        // css color spec for the stoke style
+        this.strokeStyle = '#999999';
+        // prop: fillStyle
+        // css color spec for the fill style.
+        this.fillStyle = '#999999'; 
+        
+        $.extend(true, this, options);
+    };
+    
+    $.jqplot.ShapeRenderer.prototype.init = function(options) {
+        $.extend(true, this, options);
+    };
+    
+    // function: draw
+    // draws the shape.
+    //
+    // ctx - canvas drawing context
+    // points - array of points for shapes or 
+    // [x, y, width, height] for rectangles or
+    // [x, y, radius, start angle (rad), end angle (rad)] for circles and arcs.
+    $.jqplot.ShapeRenderer.prototype.draw = function(ctx, points, options) {
+        ctx.save();
+        var opts = (options != null) ? options : {};
+        var fill = (opts.fill != null) ? opts.fill : this.fill;
+        var closePath = (opts.closePath != null) ? opts.closePath : this.closePath;
+        var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect;
+        var strokeRect = (opts.strokeRect != null) ? opts.strokeRect : this.strokeRect;
+        var clearRect = (opts.clearRect != null) ? opts.clearRect : this.clearRect;
+        var isarc = (opts.isarc != null) ? opts.isarc : this.isarc;
+        var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern;
+        var ctxPattern = $.jqplot.LinePattern(ctx, linePattern);
+        ctx.lineWidth = opts.lineWidth || this.lineWidth;
+        ctx.lineJoin = opts.lineJoin || this.lineJoin;
+        ctx.lineCap = opts.lineCap || this.lineCap;
+        ctx.strokeStyle = (opts.strokeStyle || opts.color) || this.strokeStyle;
+        ctx.fillStyle = opts.fillStyle || this.fillStyle;
+        ctx.beginPath();
+        if (isarc) {
+            ctx.arc(points[0], points[1], points[2], points[3], points[4], true);   
+            if (closePath) {
+                ctx.closePath();
+            }
+            if (fill) {
+                ctx.fill();
+            }
+            else {
+                ctx.stroke();
+            }
+            ctx.restore();
+            return;
+        }
+        else if (clearRect) {
+            ctx.clearRect(points[0], points[1], points[2], points[3]);
+            ctx.restore();
+            return;
+        }
+        else if (fillRect || strokeRect) {
+            if (fillRect) {
+                ctx.fillRect(points[0], points[1], points[2], points[3]);
+            }
+            if (strokeRect) {
+                ctx.strokeRect(points[0], points[1], points[2], points[3]);
+                ctx.restore();
+                return;
+            }
+        }
+        else if (points && points.length){
+            var move = true;
+            for (var i=0; i<points.length; i++) {
+                // skip to the first non-null point and move to it.
+                if (points[i][0] != null && points[i][1] != null) {
+                    if (move) {
+                        ctxPattern.moveTo(points[i][0], points[i][1]);
+                        move = false;
+                    }
+                    else {
+                        ctxPattern.lineTo(points[i][0], points[i][1]);
+                    }
+                }
+                else {
+                    move = true;
+                }
+            }
+            if (closePath) {
+                ctxPattern.closePath();
+            }
+            if (fill) {
+                ctx.fill();
+            }
+            else {
+                ctx.stroke();
+            }
+        }
+        ctx.restore();
+    };
+    
+    // class $.jqplot.TableLegendRenderer
+    // The default legend renderer for jqPlot.
+    $.jqplot.TableLegendRenderer = function(){
+        //
+    };
+    
+    $.jqplot.TableLegendRenderer.prototype.init = function(options) {
+        $.extend(true, this, options);
+    };
+        
+    $.jqplot.TableLegendRenderer.prototype.addrow = function (label, color, pad, reverse) {
+        var rs = (pad) ? this.rowSpacing+'px' : '0px';
+        var tr;
+        var td;
+        var elem;
+        var div0;
+        var div1;
+        elem = document.createElement('tr');
+        tr = $(elem);
+        tr.addClass('jqplot-table-legend');
+        elem = null;
+
+        if (reverse){
+            tr.prependTo(this._elem);
+        }
+
+        else{
+            tr.appendTo(this._elem);
+        }
+
+        if (this.showSwatches) {
+            td = $(document.createElement('td'));
+            td.addClass('jqplot-table-legend jqplot-table-legend-swatch');
+            td.css({textAlign: 'center', paddingTop: rs});
+
+            div0 = $(document.createElement('div'));
+            div0.addClass('jqplot-table-legend-swatch-outline');
+            div1 = $(document.createElement('div'));
+            div1.addClass('jqplot-table-legend-swatch');
+            div1.css({backgroundColor: color, borderColor: color});
+
+            tr.append(td.append(div0.append(div1)));
+
+            // $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
+            // '<div><div class="jqplot-table-legend-swatch" style="background-color:'+color+';border-color:'+color+';"></div>'+
+            // '</div></td>').appendTo(tr);
+        }
+        if (this.showLabels) {
+            td = $(document.createElement('td'));
+            td.addClass('jqplot-table-legend jqplot-table-legend-label');
+            td.css('paddingTop', rs);
+            tr.append(td);
+
+            // elem = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
+            // elem.appendTo(tr);
+            if (this.escapeHtml) {
+                td.text(label);
+            }
+            else {
+                td.html(label);
+            }
+        }
+        td = null;
+        div0 = null;
+        div1 = null;
+        tr = null;
+        elem = null;
+    };
+    
+    // called with scope of legend
+    $.jqplot.TableLegendRenderer.prototype.draw = function() {
+        if (this._elem) {
+            this._elem.emptyForce();
+            this._elem = null;
+        }
+
+        if (this.show) {
+            var series = this._series;
+            // make a table.  one line label per row.
+            var elem = document.createElement('table');
+            this._elem = $(elem);
+            this._elem.addClass('jqplot-table-legend');
+
+            var ss = {position:'absolute'};
+            if (this.background) {
+                ss['background'] = this.background;
+            }
+            if (this.border) {
+                ss['border'] = this.border;
+            }
+            if (this.fontSize) {
+                ss['fontSize'] = this.fontSize;
+            }
+            if (this.fontFamily) {
+                ss['fontFamily'] = this.fontFamily;
+            }
+            if (this.textColor) {
+                ss['textColor'] = this.textColor;
+            }
+            if (this.marginTop != null) {
+                ss['marginTop'] = this.marginTop;
+            }
+            if (this.marginBottom != null) {
+                ss['marginBottom'] = this.marginBottom;
+            }
+            if (this.marginLeft != null) {
+                ss['marginLeft'] = this.marginLeft;
+            }
+            if (this.marginRight != null) {
+                ss['marginRight'] = this.marginRight;
+            }
+            
+        
+            var pad = false, 
+                reverse = false,
+                s;
+            for (var i = 0; i< series.length; i++) {
+                s = series[i];
+                if (s._stack || s.renderer.constructor == $.jqplot.BezierCurveRenderer){
+                    reverse = true;
+                }
+                if (s.show && s.showLabel) {
+                    var lt = this.labels[i] || s.label.toString();
+                    if (lt) {
+                        var color = s.color;
+                        if (reverse && i < series.length - 1){
+                            pad = true;
+                        }
+                        else if (reverse && i == series.length - 1){
+                            pad = false;
+                        }
+                        this.renderer.addrow.call(this, lt, color, pad, reverse);
+                        pad = true;
+                    }
+                    // let plugins add more rows to legend.  Used by trend line plugin.
+                    for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) {
+                        var item = $.jqplot.addLegendRowHooks[j].call(this, s);
+                        if (item) {
+                            this.renderer.addrow.call(this, item.label, item.color, pad);
+                            pad = true;
+                        } 
+                    }
+                    lt = null;
+                }
+            }
+        }
+        return this._elem;
+    };
+    
+    $.jqplot.TableLegendRenderer.prototype.pack = function(offsets) {
+        if (this.show) {       
+            if (this.placement == 'insideGrid') {
+                switch (this.location) {
+                    case 'nw':
+                        var a = offsets.left;
+                        var b = offsets.top;
+                        this._elem.css('left', a);
+                        this._elem.css('top', b);
+                        break;
+                    case 'n':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        var b = offsets.top;
+                        this._elem.css('left', a);
+                        this._elem.css('top', b);
+                        break;
+                    case 'ne':
+                        var a = offsets.right;
+                        var b = offsets.top;
+                        this._elem.css({right:a, top:b});
+                        break;
+                    case 'e':
+                        var a = offsets.right;
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({right:a, top:b});
+                        break;
+                    case 'se':
+                        var a = offsets.right;
+                        var b = offsets.bottom;
+                        this._elem.css({right:a, bottom:b});
+                        break;
+                    case 's':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        var b = offsets.bottom;
+                        this._elem.css({left:a, bottom:b});
+                        break;
+                    case 'sw':
+                        var a = offsets.left;
+                        var b = offsets.bottom;
+                        this._elem.css({left:a, bottom:b});
+                        break;
+                    case 'w':
+                        var a = offsets.left;
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({left:a, top:b});
+                        break;
+                    default:  // same as 'se'
+                        var a = offsets.right;
+                        var b = offsets.bottom;
+                        this._elem.css({right:a, bottom:b});
+                        break;
+                }
+                
+            }
+            else if (this.placement == 'outside'){
+                switch (this.location) {
+                    case 'nw':
+                        var a = this._plotDimensions.width - offsets.left;
+                        var b = offsets.top;
+                        this._elem.css('right', a);
+                        this._elem.css('top', b);
+                        break;
+                    case 'n':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        var b = this._plotDimensions.height - offsets.top;
+                        this._elem.css('left', a);
+                        this._elem.css('bottom', b);
+                        break;
+                    case 'ne':
+                        var a = this._plotDimensions.width - offsets.right;
+                        var b = offsets.top;
+                        this._elem.css({left:a, top:b});
+                        break;
+                    case 'e':
+                        var a = this._plotDimensions.width - offsets.right;
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({left:a, top:b});
+                        break;
+                    case 'se':
+                        var a = this._plotDimensions.width - offsets.right;
+                        var b = offsets.bottom;
+                        this._elem.css({left:a, bottom:b});
+                        break;
+                    case 's':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        var b = this._plotDimensions.height - offsets.bottom;
+                        this._elem.css({left:a, top:b});
+                        break;
+                    case 'sw':
+                        var a = this._plotDimensions.width - offsets.left;
+                        var b = offsets.bottom;
+                        this._elem.css({right:a, bottom:b});
+                        break;
+                    case 'w':
+                        var a = this._plotDimensions.width - offsets.left;
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({right:a, top:b});
+                        break;
+                    default:  // same as 'se'
+                        var a = offsets.right;
+                        var b = offsets.bottom;
+                        this._elem.css({right:a, bottom:b});
+                        break;
+                }
+            }
+            else {
+                switch (this.location) {
+                    case 'nw':
+                        this._elem.css({left:0, top:offsets.top});
+                        break;
+                    case 'n':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        this._elem.css({left: a, top:offsets.top});
+                        break;
+                    case 'ne':
+                        this._elem.css({right:0, top:offsets.top});
+                        break;
+                    case 'e':
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({right:offsets.right, top:b});
+                        break;
+                    case 'se':
+                        this._elem.css({right:offsets.right, bottom:offsets.bottom});
+                        break;
+                    case 's':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        this._elem.css({left: a, bottom:offsets.bottom});
+                        break;
+                    case 'sw':
+                        this._elem.css({left:offsets.left, bottom:offsets.bottom});
+                        break;
+                    case 'w':
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({left:offsets.left, top:b});
+                        break;
+                    default:  // same as 'se'
+                        this._elem.css({right:offsets.right, bottom:offsets.bottom});
+                        break;
+                }
+            }
+        } 
+    };
+
+    /**
+     * Class: $.jqplot.ThemeEngine
+     * Theme Engine provides a programatic way to change some of the  more
+     * common jqplot styling options such as fonts, colors and grid options.
+     * A theme engine instance is created with each plot.  The theme engine
+     * manages a collection of themes which can be modified, added to, or 
+     * applied to the plot.
+     * 
+     * The themeEngine class is not instantiated directly.
+     * When a plot is initialized, the current plot options are scanned
+     * an a default theme named "Default" is created.  This theme is
+     * used as the basis for other themes added to the theme engine and
+     * is always available.
+     * 
+     * A theme is a simple javascript object with styling parameters for
+     * various entities of the plot.  A theme has the form:
+     * 
+     * 
+     * > {
+     * >     _name:f "Default",
+     * >     target: {
+     * >         backgroundColor: "transparent"
+     * >     },
+     * >     legend: {
+     * >         textColor: null,
+     * >         fontFamily: null,
+     * >         fontSize: null,
+     * >         border: null,
+     * >         background: null
+     * >     },
+     * >     title: {
+     * >         textColor: "rgb(102, 102, 102)",
+     * >         fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif",
+     * >         fontSize: "19.2px",
+     * >         textAlign: "center"
+     * >     },
+     * >     seriesStyles: {},
+     * >     series: [{
+     * >         color: "#4bb2c5",
+     * >         lineWidth: 2.5,
+     * >         linePattern: "solid",
+     * >         shadow: true,
+     * >         fillColor: "#4bb2c5",
+     * >         showMarker: true,
+     * >         markerOptions: {
+     * >             color: "#4bb2c5",
+     * >             show: true,
+     * >             style: 'filledCircle',
+     * >             lineWidth: 1.5,
+     * >             size: 4,
+     * >             shadow: true
+     * >         }
+     * >     }],
+     * >     grid: {
+     * >         drawGridlines: true,
+     * >         gridLineColor: "#cccccc",
+     * >         gridLineWidth: 1,
+     * >         backgroundColor: "#fffdf6",
+     * >         borderColor: "#999999",
+     * >         borderWidth: 2,
+     * >         shadow: true
+     * >     },
+     * >     axesStyles: {
+     * >         label: {},
+     * >         ticks: {}
+     * >     },
+     * >     axes: {
+     * >         xaxis: {
+     * >             borderColor: "#999999",
+     * >             borderWidth: 2,
+     * >             ticks: {
+     * >                 show: true,
+     * >                 showGridline: true,
+     * >                 showLabel: true,
+     * >                 showMark: true,
+     * >                 size: 4,
+     * >                 textColor: "",
+     * >                 whiteSpace: "nowrap",
+     * >                 fontSize: "12px",
+     * >                 fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif"
+     * >             },
+     * >             label: {
+     * >                 textColor: "rgb(102, 102, 102)",
+     * >                 whiteSpace: "normal",
+     * >                 fontSize: "14.6667px",
+     * >                 fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif",
+     * >                 fontWeight: "400"
+     * >             }
+     * >         },
+     * >         yaxis: {
+     * >             borderColor: "#999999",
+     * >             borderWidth: 2,
+     * >             ticks: {
+     * >                 show: true,
+     * >                 showGridline: true,
+     * >                 showLabel: true,
+     * >                 showMark: true,
+     * >                 size: 4,
+     * >                 textColor: "",
+     * >                 whiteSpace: "nowrap",
+     * >                 fontSize: "12px",
+     * >                 fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif"
+     * >             },
+     * >             label: {
+     * >                 textColor: null,
+     * >                 whiteSpace: null,
+     * >                 fontSize: null,
+     * >                 fontFamily: null,
+     * >                 fontWeight: null
+     * >             }
+     * >         },
+     * >         x2axis: {...
+     * >         },
+     * >         ...
+     * >         y9axis: {...
+     * >         }
+     * >     }
+     * > }
+     * 
+     * "seriesStyles" is a style object that will be applied to all series in the plot.
+     * It will forcibly override any styles applied on the individual series.  "axesStyles" is
+     * a style object that will be applied to all axes in the plot.  It will also forcibly
+     * override any styles on the individual axes.
+     * 
+     * The example shown above has series options for a line series.  Options for other
+     * series types are shown below:
+     * 
+     * Bar Series:
+     * 
+     * > {
+     * >     color: "#4bb2c5",
+     * >     seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
+     * >     lineWidth: 2.5,
+     * >     shadow: true,
+     * >     barPadding: 2,
+     * >     barMargin: 10,
+     * >     barWidth: 15.09375,
+     * >     highlightColors: ["rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)"]
+     * > }
+     * 
+     * Pie Series:
+     * 
+     * > {
+     * >     seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
+     * >     padding: 20,
+     * >     sliceMargin: 0,
+     * >     fill: true,
+     * >     shadow: true,
+     * >     startAngle: 0,
+     * >     lineWidth: 2.5,
+     * >     highlightColors: ["rgb(129,201,214)", "rgb(240,189,104)", "rgb(214,202,165)", "rgb(137,180,158)", "rgb(168,180,137)", "rgb(180,174,89)", "rgb(180,113,161)", "rgb(129,141,236)", "rgb(227,205,120)", "rgb(255,138,76)", "rgb(76,169,219)", "rgb(215,126,190)", "rgb(220,232,135)", "rgb(200,167,96)", "rgb(103,202,235)", "rgb(208,154,215)"]
+     * > }
+     * 
+     * Funnel Series:
+     * 
+     * > {
+     * >     color: "#4bb2c5",
+     * >     lineWidth: 2,
+     * >     shadow: true,
+     * >     padding: {
+     * >         top: 20,
+     * >         right: 20,
+     * >         bottom: 20,
+     * >         left: 20
+     * >     },
+     * >     sectionMargin: 6,
+     * >     seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
+     * >     highlightColors: ["rgb(147,208,220)", "rgb(242,199,126)", "rgb(220,210,178)", "rgb(154,191,172)", "rgb(180,191,154)", "rgb(191,186,112)", "rgb(191,133,174)", "rgb(147,157,238)", "rgb(231,212,139)", "rgb(255,154,102)", "rgb(102,181,224)", "rgb(221,144,199)", "rgb(225,235,152)", "rgb(200,167,96)", "rgb(124,210,238)", "rgb(215,169,221)"]
+     * > }
+     * 
+     */
+    $.jqplot.ThemeEngine = function(){
+        // Group: Properties
+        //
+        // prop: themes
+        // hash of themes managed by the theme engine.  
+        // Indexed by theme name.
+        this.themes = {};
+        // prop: activeTheme
+        // Pointer to currently active theme
+        this.activeTheme=null;
+        
+    };
+    
+    // called with scope of plot
+    $.jqplot.ThemeEngine.prototype.init = function() {
+        // get the Default theme from the current plot settings.
+        var th = new $.jqplot.Theme({_name:'Default'});
+        var n, i, nn;
+        
+        for (n in th.target) {
+            if (n == "textColor") {
+                th.target[n] = this.target.css('color');
+            }
+            else {
+                th.target[n] = this.target.css(n);
+            }
+        }
+        
+        if (this.title.show && this.title._elem) {
+            for (n in th.title) {
+                if (n == "textColor") {
+                    th.title[n] = this.title._elem.css('color');
+                }
+                else {
+                    th.title[n] = this.title._elem.css(n);
+                }
+            }
+        }
+        
+        for (n in th.grid) {
+            th.grid[n] = this.grid[n];
+        }
+        if (th.grid.backgroundColor == null && this.grid.background != null) {
+            th.grid.backgroundColor = this.grid.background;
+        }
+        if (this.legend.show && this.legend._elem) {
+            for (n in th.legend) {
+                if (n == 'textColor') {
+                    th.legend[n] = this.legend._elem.css('color');
+                }
+                else {
+                    th.legend[n] = this.legend._elem.css(n);
+                }
+            }
+        }
+        var s;
+        
+        for (i=0; i<this.series.length; i++) {
+            s = this.series[i];
+            if (s.renderer.constructor == $.jqplot.LineRenderer) {
+                th.series.push(new LineSeriesProperties());
+            }
+            else if (s.renderer.constructor == $.jqplot.BarRenderer) {
+                th.series.push(new BarSeriesProperties());
+            }
+            else if (s.renderer.constructor == $.jqplot.PieRenderer) {
+                th.series.push(new PieSeriesProperties());
+            }
+            else if (s.renderer.constructor == $.jqplot.DonutRenderer) {
+                th.series.push(new DonutSeriesProperties());
+            }
+            else if (s.renderer.constructor == $.jqplot.FunnelRenderer) {
+                th.series.push(new FunnelSeriesProperties());
+            }
+            else if (s.renderer.constructor == $.jqplot.MeterGaugeRenderer) {
+                th.series.push(new MeterSeriesProperties());
+            }
+            else {
+                th.series.push({});
+            }
+            for (n in th.series[i]) {
+                th.series[i][n] = s[n];
+            }
+        }
+        var a, ax;
+        for (n in this.axes) {
+            ax = this.axes[n];
+            a = th.axes[n] = new AxisProperties();
+            a.borderColor = ax.borderColor;
+            a.borderWidth = ax.borderWidth;
+            if (ax._ticks && ax._ticks[0]) {
+                for (nn in a.ticks) {
+                    if (ax._ticks[0].hasOwnProperty(nn)) {
+                        a.ticks[nn] = ax._ticks[0][nn];
+                    }
+                    else if (ax._ticks[0]._elem){
+                        a.ticks[nn] = ax._ticks[0]._elem.css(nn);
+                    }
+                }
+            }
+            if (ax._label && ax._label.show) {
+                for (nn in a.label) {
+                    // a.label[nn] = ax._label._elem.css(nn);
+                    if (ax._label[nn]) {
+                        a.label[nn] = ax._label[nn];
+                    }
+                    else if (ax._label._elem){
+                        if (nn == 'textColor') {
+                            a.label[nn] = ax._label._elem.css('color');
+                        }
+                        else {
+                            a.label[nn] = ax._label._elem.css(nn);
+                        }
+                    }
+                }
+            }
+        }
+        this.themeEngine._add(th);
+        this.themeEngine.activeTheme  = this.themeEngine.themes[th._name];
+    };
+    /**
+     * Group: methods
+     * 
+     * method: get
+     * 
+     * Get and return the named theme or the active theme if no name given.
+     * 
+     * parameter:
+     * 
+     * name - name of theme to get.
+     * 
+     * returns:
+     * 
+     * Theme instance of given name.
+     */   
+    $.jqplot.ThemeEngine.prototype.get = function(name) {
+        if (!name) {
+            // return the active theme
+            return this.activeTheme;
+        }
+        else {
+            return this.themes[name];
+        }
+    };
+    
+    function numericalOrder(a,b) { return a-b; }
+    
+    /**
+     * method: getThemeNames
+     * 
+     * Return the list of theme names in this manager in alpha-numerical order.
+     * 
+     * parameter:
+     * 
+     * None
+     * 
+     * returns:
+     * 
+     * A the list of theme names in this manager in alpha-numerical order.
+     */       
+    $.jqplot.ThemeEngine.prototype.getThemeNames = function() {
+        var tn = [];
+        for (var n in this.themes) {
+            tn.push(n);
+        }
+        return tn.sort(numericalOrder);
+    };
+
+    /**
+     * method: getThemes
+     * 
+     * Return a list of themes in alpha-numerical order by name.
+     * 
+     * parameter:
+     * 
+     * None
+     * 
+     * returns:
+     * 
+     * A list of themes in alpha-numerical order by name.
+     */ 
+    $.jqplot.ThemeEngine.prototype.getThemes = function() {
+        var tn = [];
+        var themes = [];
+        for (var n in this.themes) {
+            tn.push(n);
+        }
+        tn.sort(numericalOrder);
+        for (var i=0; i<tn.length; i++) {
+            themes.push(this.themes[tn[i]]);
+        }
+        return themes;
+    };
+    
+    $.jqplot.ThemeEngine.prototype.activate = function(plot, name) {
+        // sometimes need to redraw whole plot.
+        var redrawPlot = false;
+        if (!name && this.activeTheme && this.activeTheme._name) {
+            name = this.activeTheme._name;
+        }
+        if (!this.themes.hasOwnProperty(name)) {
+            throw new Error("No theme of that name");
+        }
+        else {
+            var th = this.themes[name];
+            this.activeTheme = th;
+            var val, checkBorderColor = false, checkBorderWidth = false;
+            var arr = ['xaxis', 'x2axis', 'yaxis', 'y2axis'];
+            
+            for (i=0; i<arr.length; i++) {
+                var ax = arr[i];
+                if (th.axesStyles.borderColor != null) {
+                    plot.axes[ax].borderColor = th.axesStyles.borderColor;
+                }
+                if (th.axesStyles.borderWidth != null) {
+                    plot.axes[ax].borderWidth = th.axesStyles.borderWidth;
+                }
+            }
+            
+            for (var axname in plot.axes) {
+                var axis = plot.axes[axname];
+                if (axis.show) {
+                    var thaxis = th.axes[axname] || {};
+                    var thaxstyle = th.axesStyles;
+                    var thax = $.jqplot.extend(true, {}, thaxis, thaxstyle);
+                    val = (th.axesStyles.borderColor != null) ? th.axesStyles.borderColor : thax.borderColor;
+                    if (thax.borderColor != null) {
+                        axis.borderColor = thax.borderColor;
+                        redrawPlot = true;
+                    }
+                    val = (th.axesStyles.borderWidth != null) ? th.axesStyles.borderWidth : thax.borderWidth;
+                    if (thax.borderWidth != null) {
+                        axis.borderWidth = thax.borderWidth;
+                        redrawPlot = true;
+                    }
+                    if (axis._ticks && axis._ticks[0]) {
+                        for (var nn in thax.ticks) {
+                            // val = null;
+                            // if (th.axesStyles.ticks && th.axesStyles.ticks[nn] != null) {
+                            //     val = th.axesStyles.ticks[nn];
+                            // }
+                            // else if (thax.ticks[nn] != null){
+                            //     val = thax.ticks[nn]
+                            // }
+                            val = thax.ticks[nn];
+                            if (val != null) {
+                                axis.tickOptions[nn] = val;
+                                axis._ticks = [];
+                                redrawPlot = true;
+                            }
+                        }
+                    }
+                    if (axis._label && axis._label.show) {
+                        for (var nn in thax.label) {
+                            // val = null;
+                            // if (th.axesStyles.label && th.axesStyles.label[nn] != null) {
+                            //     val = th.axesStyles.label[nn];
+                            // }
+                            // else if (thax.label && thax.label[nn] != null){
+                            //     val = thax.label[nn]
+                            // }
+                            val = thax.label[nn];
+                            if (val != null) {
+                                axis.labelOptions[nn] = val;
+                                redrawPlot = true;
+                            }
+                        }
+                    }
+                    
+                }
+            }            
+            
+            for (var n in th.grid) {
+                if (th.grid[n] != null) {
+                    plot.grid[n] = th.grid[n];
+                }
+            }
+            if (!redrawPlot) {
+                plot.grid.draw();
+            }
+            
+            if (plot.legend.show) { 
+                for (n in th.legend) {
+                    if (th.legend[n] != null) {
+                        plot.legend[n] = th.legend[n];
+                    }
+                }
+            }
+            if (plot.title.show) {
+                for (n in th.title) {
+                    if (th.title[n] != null) {
+                        plot.title[n] = th.title[n];
+                    }
+                }
+            }
+            
+            var i;
+            for (i=0; i<th.series.length; i++) {
+                var opts = {};
+                var redrawSeries = false;
+                for (n in th.series[i]) {
+                    val = (th.seriesStyles[n] != null) ? th.seriesStyles[n] : th.series[i][n];
+                    if (val != null) {
+                        opts[n] = val;
+                        if (n == 'color') {
+                            plot.series[i].renderer.shapeRenderer.fillStyle = val;
+                            plot.series[i].renderer.shapeRenderer.strokeStyle = val;
+                            plot.series[i][n] = val;
+                        }
+                        else if ((n == 'lineWidth') || (n == 'linePattern')) {
+                            plot.series[i].renderer.shapeRenderer[n] = val;
+                            plot.series[i][n] = val;
+                        }
+                        else if (n == 'markerOptions') {
+                            merge (plot.series[i].markerOptions, val);
+                            merge (plot.series[i].markerRenderer, val);
+                        }
+                        else {
+                            plot.series[i][n] = val;
+                        }
+                        redrawPlot = true;
+                    }
+                }
+            }
+            
+            if (redrawPlot) {
+                plot.target.empty();
+                plot.draw();
+            }
+            
+            for (n in th.target) {
+                if (th.target[n] != null) {
+                    plot.target.css(n, th.target[n]);
+                }
+            }
+        }
+        
+    };
+    
+    $.jqplot.ThemeEngine.prototype._add = function(theme, name) {
+        if (name) {
+            theme._name = name;
+        }
+        if (!theme._name) {
+            theme._name = Date.parse(new Date());
+        }
+        if (!this.themes.hasOwnProperty(theme._name)) {
+            this.themes[theme._name] = theme;
+        }
+        else {
+            throw new Error("jqplot.ThemeEngine Error: Theme already in use");
+        }
+    };
+    
+    // method remove
+    // Delete the named theme, return true on success, false on failure.
+    
+
+    /**
+     * method: remove
+     * 
+     * Remove the given theme from the themeEngine.
+     * 
+     * parameters:
+     * 
+     * name - name of the theme to remove.
+     * 
+     * returns:
+     * 
+     * true on success, false on failure.
+     */
+    $.jqplot.ThemeEngine.prototype.remove = function(name) {
+        if (name == 'Default') {
+            return false;
+        }
+        return delete this.themes[name];
+    };
+
+    /**
+     * method: newTheme
+     * 
+     * Create a new theme based on the default theme, adding it the themeEngine.
+     * 
+     * parameters:
+     * 
+     * name - name of the new theme.
+     * obj - optional object of styles to be applied to this new theme.
+     * 
+     * returns:
+     * 
+     * new Theme object.
+     */
+    $.jqplot.ThemeEngine.prototype.newTheme = function(name, obj) {
+        if (typeof(name) == 'object') {
+            obj = obj || name;
+            name = null;
+        }
+        if (obj && obj._name) {
+            name = obj._name;
+        }
+        else {
+            name = name || Date.parse(new Date());
+        }
+        // var th = new $.jqplot.Theme(name);
+        var th = this.copy(this.themes['Default']._name, name);
+        $.jqplot.extend(th, obj);
+        return th;
+    };
+    
+    // function clone(obj) {
+    //     return eval(obj.toSource());
+    // }
+    
+    function clone(obj){
+        if(obj == null || typeof(obj) != 'object'){
+            return obj;
+        }
+    
+        var temp = new obj.constructor();
+        for(var key in obj){
+            temp[key] = clone(obj[key]);
+        }   
+        return temp;
+    }
+    
+    $.jqplot.clone = clone;
+    
+    function merge(obj1, obj2) {
+        if (obj2 ==  null || typeof(obj2) != 'object') {
+            return;
+        }
+        for (var key in obj2) {
+            if (key == 'highlightColors') {
+                obj1[key] = clone(obj2[key]);
+            }
+            if (obj2[key] != null && typeof(obj2[key]) == 'object') {
+                if (!obj1.hasOwnProperty(key)) {
+                    obj1[key] = {};
+                }
+                merge(obj1[key], obj2[key]);
+            }
+            else {
+                obj1[key] = obj2[key];
+            }
+        }
+    }
+    
+    $.jqplot.merge = merge;
+    
+        // Use the jQuery 1.3.2 extend function since behaviour in jQuery 1.4 seems problematic
+    $.jqplot.extend = function() {
+        // copy reference to target object
+        var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+        // Handle a deep copy situation
+        if ( typeof target === "boolean" ) {
+            deep = target;
+            target = arguments[1] || {};
+            // skip the boolean and the target
+            i = 2;
+        }
+
+        // Handle case when target is a string or something (possible in deep copy)
+        if ( typeof target !== "object" && !toString.call(target) === "[object Function]" ) {
+            target = {};
+        }
+
+        for ( ; i < length; i++ ){
+            // Only deal with non-null/undefined values
+            if ( (options = arguments[ i ]) != null ) {
+                // Extend the base object
+                for ( var name in options ) {
+                    var src = target[ name ], copy = options[ name ];
+
+                    // Prevent never-ending loop
+                    if ( target === copy ) {
+                        continue;
+                    }
+
+                    // Recurse if we're merging object values
+                    if ( deep && copy && typeof copy === "object" && !copy.nodeType ) {
+                        target[ name ] = $.jqplot.extend( deep, 
+                            // Never move original objects, clone them
+                            src || ( copy.length != null ? [ ] : { } )
+                        , copy );
+                    }
+                    // Don't bring in undefined values
+                    else if ( copy !== undefined ) {
+                        target[ name ] = copy;
+                    }
+                }
+            }
+        }
+        // Return the modified object
+        return target;
+    };
+
+    /**
+     * method: rename
+     * 
+     * Rename a theme.
+     * 
+     * parameters:
+     * 
+     * oldName - current name of the theme.
+     * newName - desired name of the theme.
+     * 
+     * returns:
+     * 
+     * new Theme object.
+     */
+    $.jqplot.ThemeEngine.prototype.rename = function (oldName, newName) {
+        if (oldName == 'Default' || newName == 'Default') {
+            throw new Error ("jqplot.ThemeEngine Error: Cannot rename from/to Default");
+        }
+        if (this.themes.hasOwnProperty(newName)) {
+            throw new Error ("jqplot.ThemeEngine Error: New name already in use.");
+        }
+        else if (this.themes.hasOwnProperty(oldName)) {
+            var th = this.copy (oldName, newName);
+            this.remove(oldName);
+            return th;
+        }
+        throw new Error("jqplot.ThemeEngine Error: Old name or new name invalid");
+    };
+
+    /**
+     * method: copy
+     * 
+     * Create a copy of an existing theme in the themeEngine, adding it the themeEngine.
+     * 
+     * parameters:
+     * 
+     * sourceName - name of the existing theme.
+     * targetName - name of the copy.
+     * obj - optional object of style parameter to apply to the new theme.
+     * 
+     * returns:
+     * 
+     * new Theme object.
+     */
+    $.jqplot.ThemeEngine.prototype.copy = function (sourceName, targetName, obj) {
+        if (targetName == 'Default') {
+            throw new Error ("jqplot.ThemeEngine Error: Cannot copy over Default theme");
+        }
+        if (!this.themes.hasOwnProperty(sourceName)) {
+            var s = "jqplot.ThemeEngine Error: Source name invalid";
+            throw new Error(s);
+        }
+        if (this.themes.hasOwnProperty(targetName)) {
+            var s = "jqplot.ThemeEngine Error: Target name invalid";
+            throw new Error(s);
+        }
+        else {
+            var th = clone(this.themes[sourceName]);
+            th._name = targetName;
+            $.jqplot.extend(true, th, obj);
+            this._add(th);
+            return th;
+        }
+    };
+    
+    
+    $.jqplot.Theme = function(name, obj) {
+        if (typeof(name) == 'object') {
+            obj = obj || name;
+            name = null;
+        }
+        name = name || Date.parse(new Date());
+        this._name = name;
+        this.target = {
+            backgroundColor: null
+        };
+        this.legend = {
+            textColor: null,
+            fontFamily: null,
+            fontSize: null,
+            border: null,
+            background: null
+        };
+        this.title = {
+            textColor: null,
+            fontFamily: null,
+            fontSize: null,
+            textAlign: null
+        };
+        this.seriesStyles = {};
+        this.series = [];
+        this.grid = {
+            drawGridlines: null,
+            gridLineColor: null,
+            gridLineWidth: null,
+            backgroundColor: null,
+            borderColor: null,
+            borderWidth: null,
+            shadow: null
+        };
+        this.axesStyles = {label:{}, ticks:{}};
+        this.axes = {};
+        if (typeof(obj) == 'string') {
+            this._name = obj;
+        }
+        else if(typeof(obj) == 'object') {
+            $.jqplot.extend(true, this, obj);
+        }
+    };
+    
+    var AxisProperties = function() {
+        this.borderColor = null;
+        this.borderWidth = null;
+        this.ticks = new AxisTicks();
+        this.label = new AxisLabel();
+    };
+    
+    var AxisTicks = function() {
+        this.show = null;
+        this.showGridline = null;
+        this.showLabel = null;
+        this.showMark = null;
+        this.size = null;
+        this.textColor = null;
+        this.whiteSpace = null;
+        this.fontSize = null;
+        this.fontFamily = null;
+    };
+    
+    var AxisLabel = function() {
+        this.textColor = null;
+        this.whiteSpace = null;
+        this.fontSize = null;
+        this.fontFamily = null;
+        this.fontWeight = null;
+    };
+    
+    var LineSeriesProperties = function() {
+        this.color=null;
+        this.lineWidth=null;
+        this.linePattern=null;
+        this.shadow=null;
+        this.fillColor=null;
+        this.showMarker=null;
+        this.markerOptions = new MarkerOptions();
+    };
+    
+    var MarkerOptions = function() {
+        this.show = null;
+        this.style = null;
+        this.lineWidth = null;
+        this.size = null;
+        this.color = null;
+        this.shadow = null;
+    };
+    
+    var BarSeriesProperties = function() {
+        this.color=null;
+        this.seriesColors=null;
+        this.lineWidth=null;
+        this.shadow=null;
+        this.barPadding=null;
+        this.barMargin=null;
+        this.barWidth=null;
+        this.highlightColors=null;
+    };
+    
+    var PieSeriesProperties = function() {
+        this.seriesColors=null;
+        this.padding=null;
+        this.sliceMargin=null;
+        this.fill=null;
+        this.shadow=null;
+        this.startAngle=null;
+        this.lineWidth=null;
+        this.highlightColors=null;
+    };
+    
+    var DonutSeriesProperties = function() {
+        this.seriesColors=null;
+        this.padding=null;
+        this.sliceMargin=null;
+        this.fill=null;
+        this.shadow=null;
+        this.startAngle=null;
+        this.lineWidth=null;
+        this.innerDiameter=null;
+        this.thickness=null;
+        this.ringMargin=null;
+        this.highlightColors=null;
+    };
+    
+    var FunnelSeriesProperties = function() {
+        this.color=null;
+        this.lineWidth=null;
+        this.shadow=null;
+        this.padding=null;
+        this.sectionMargin=null;
+        this.seriesColors=null;
+        this.highlightColors=null;
+    };
+    
+    var MeterSeriesProperties = function() {
+        this.padding=null;
+        this.backgroundColor=null;
+        this.ringColor=null;
+        this.tickColor=null;
+        this.ringWidth=null;
+        this.intervalColors=null;
+        this.intervalInnerRadius=null;
+        this.intervalOuterRadius=null;
+        this.hubRadius=null;
+        this.needleThickness=null;
+        this.needlePad=null;
+    };
+        
+
+
+
+    $.fn.jqplotChildText = function() {
+        return $(this).contents().filter(function() {
+            return this.nodeType == 3;  // Node.TEXT_NODE not defined in I7
+        }).text();
+    };
+
+    // Returns font style as abbreviation for "font" property.
+    $.fn.jqplotGetComputedFontStyle = function() {
+        var css = window.getComputedStyle ?  window.getComputedStyle(this[0], "") : this[0].currentStyle;
+        var attrs = css['font-style'] ? ['font-style', 'font-weight', 'font-size', 'font-family'] : ['fontStyle', 'fontWeight', 'fontSize', 'fontFamily'];
+        var style = [];
+
+        for (var i=0 ; i < attrs.length; ++i) {
+            var attr = String(css[attrs[i]]);
+
+            if (attr && attr != 'normal') {
+                style.push(attr);
+            }
+        }
+        return style.join(' ');
+    };
+
+    /**
+     * Namespace: $.fn
+     * jQuery namespace to attach functions to jQuery elements.
+     *  
+     */
+
+    $.fn.jqplotToImageCanvas = function(options) {
+
+        options = options || {};
+        var x_offset = (options.x_offset == null) ? 0 : options.x_offset;
+        var y_offset = (options.y_offset == null) ? 0 : options.y_offset;
+        var backgroundColor = (options.backgroundColor == null) ? 'rgb(255,255,255)' : options.backgroundColor;
+
+        if ($(this).width() == 0 || $(this).height() == 0) {
+            return null;
+        }
+
+        // excanvas and hence IE < 9 do not support toDataURL and cannot export images.
+        if ($.jqplot.use_excanvas) {
+            return null;
+        }
+        
+        var newCanvas = document.createElement("canvas");
+        var h = $(this).outerHeight(true);
+        var w = $(this).outerWidth(true);
+        var offs = $(this).offset();
+        var plotleft = offs.left;
+        var plottop = offs.top;
+        var transx = 0, transy = 0;
+
+        // have to check if any elements are hanging outside of plot area before rendering,
+        // since changing width of canvas will erase canvas.
+
+        var clses = ['jqplot-table-legend', 'jqplot-xaxis-tick', 'jqplot-x2axis-tick', 'jqplot-yaxis-tick', 'jqplot-y2axis-tick', 'jqplot-y3axis-tick', 
+        'jqplot-y4axis-tick', 'jqplot-y5axis-tick', 'jqplot-y6axis-tick', 'jqplot-y7axis-tick', 'jqplot-y8axis-tick', 'jqplot-y9axis-tick',
+        'jqplot-xaxis-label', 'jqplot-x2axis-label', 'jqplot-yaxis-label', 'jqplot-y2axis-label', 'jqplot-y3axis-label', 'jqplot-y4axis-label', 
+        'jqplot-y5axis-label', 'jqplot-y6axis-label', 'jqplot-y7axis-label', 'jqplot-y8axis-label', 'jqplot-y9axis-label' ];
+
+        var temptop, templeft, tempbottom, tempright;
+
+        for (var i = 0; i < clses.length; i++) {
+            $(this).find('.'+clses[i]).each(function() {
+                temptop = $(this).offset().top - plottop;
+                templeft = $(this).offset().left - plotleft;
+                tempright = templeft + $(this).outerWidth(true) + transx;
+                tempbottom = temptop + $(this).outerHeight(true) + transy;
+                if (templeft < -transx) {
+                    w = w - transx - templeft;
+                    transx = -templeft;
+                }
+                if (temptop < -transy) {
+                    h = h - transy - temptop;
+                    transy = - temptop;
+                }
+                if (tempright > w) {
+                    w = tempright;
+                }
+                if (tempbottom > h) {
+                    h =  tempbottom;
+                }
+            });
+        }
+
+        newCanvas.width = w + Number(x_offset);
+        newCanvas.height = h + Number(y_offset);
+
+        var newContext = newCanvas.getContext("2d"); 
+
+        newContext.save();
+        newContext.fillStyle = backgroundColor;
+        newContext.fillRect(0,0, newCanvas.width, newCanvas.height);
+        newContext.restore();
+
+        newContext.translate(transx, transy);
+        newContext.textAlign = 'left';
+        newContext.textBaseline = 'top';
+
+        function getLineheight(el) {
+            var lineheight = parseInt($(el).css('line-height'), 10);
+
+            if (isNaN(lineheight)) {
+                lineheight = parseInt($(el).css('font-size'), 10) * 1.2;
+            }
+            return lineheight;
+        }
+
+        function writeWrappedText (el, context, text, left, top, canvasWidth) {
+            var lineheight = getLineheight(el);
+            var tagwidth = $(el).innerWidth();
+            var tagheight = $(el).innerHeight();
+            var words = text.split(/\s+/);
+            var wl = words.length;
+            var w = '';
+            var breaks = [];
+            var temptop = top;
+            var templeft = left;
+
+            for (var i=0; i<wl; i++) {
+                w += words[i];
+                if (context.measureText(w).width > tagwidth) {
+                    breaks.push(i);
+                    w = '';
+                    i--;
+                }   
+            }
+            if (breaks.length === 0) {
+                // center text if necessary
+                if ($(el).css('textAlign') === 'center') {
+                    templeft = left + (canvasWidth - context.measureText(w).width)/2  - transx;
+                }
+                context.fillText(text, templeft, top);
+            }
+            else {
+                w = words.slice(0, breaks[0]).join(' ');
+                // center text if necessary
+                if ($(el).css('textAlign') === 'center') {
+                    templeft = left + (canvasWidth - context.measureText(w).width)/2  - transx;
+                }
+                context.fillText(w, templeft, temptop);
+                temptop += lineheight;
+                for (var i=1, l=breaks.length; i<l; i++) {
+                    w = words.slice(breaks[i-1], breaks[i]).join(' ');
+                    // center text if necessary
+                    if ($(el).css('textAlign') === 'center') {
+                        templeft = left + (canvasWidth - context.measureText(w).width)/2  - transx;
+                    }
+                    context.fillText(w, templeft, temptop);
+                    temptop += lineheight;
+                }
+                w = words.slice(breaks[i-1], words.length).join(' ');
+                // center text if necessary
+                if ($(el).css('textAlign') === 'center') {
+                    templeft = left + (canvasWidth - context.measureText(w).width)/2  - transx;
+                }
+                context.fillText(w, templeft, temptop);
+            }
+
+        }
+
+        function _jqpToImage(el, x_offset, y_offset) {
+            var tagname = el.tagName.toLowerCase();
+            var p = $(el).position();
+            var css = window.getComputedStyle ?  window.getComputedStyle(el, "") : el.currentStyle; // for IE < 9
+            var left = x_offset + p.left + parseInt(css.marginLeft, 10) + parseInt(css.borderLeftWidth, 10) + parseInt(css.paddingLeft, 10);
+            var top = y_offset + p.top + parseInt(css.marginTop, 10) + parseInt(css.borderTopWidth, 10)+ parseInt(css.paddingTop, 10);
+            var w = newCanvas.width;
+            // var left = x_offset + p.left + $(el).css('marginLeft') + $(el).css('borderLeftWidth') 
+
+            // somehow in here, for divs within divs, the width of the inner div should be used instead of the canvas.
+
+            if ((tagname == 'div' || tagname == 'span') && !$(el).hasClass('jqplot-highlighter-tooltip')) {
+                $(el).children().each(function() {
+                    _jqpToImage(this, left, top);
+                });
+                var text = $(el).jqplotChildText();
+
+                if (text) {
+                    newContext.font = $(el).jqplotGetComputedFontStyle();
+                    newContext.fillStyle = $(el).css('color');
+
+                    writeWrappedText(el, newContext, text, left, top, w);
+                }
+            }
+
+            // handle the standard table legend
+
+            else if (tagname === 'table' && $(el).hasClass('jqplot-table-legend')) {
+                newContext.strokeStyle = $(el).css('border-top-color');
+                newContext.fillStyle = $(el).css('background-color');
+                newContext.fillRect(left, top, $(el).innerWidth(), $(el).innerHeight());
+                if (parseInt($(el).css('border-top-width'), 10) > 0) {
+                    newContext.strokeRect(left, top, $(el).innerWidth(), $(el).innerHeight());
+                }
+
+                // find all the swatches
+                $(el).find('div.jqplot-table-legend-swatch-outline').each(function() {
+                    // get the first div and stroke it
+                    var elem = $(this);
+                    newContext.strokeStyle = elem.css('border-top-color');
+                    var l = left + elem.position().left;
+                    var t = top + elem.position().top;
+                    newContext.strokeRect(l, t, elem.innerWidth(), elem.innerHeight());
+
+                    // now fill the swatch
+                    
+                    l += parseInt(elem.css('padding-left'), 10);
+                    t += parseInt(elem.css('padding-top'), 10);
+                    var h = elem.innerHeight() - 2 * parseInt(elem.css('padding-top'), 10);
+                    var w = elem.innerWidth() - 2 * parseInt(elem.css('padding-left'), 10);
+
+                    var swatch = elem.children('div.jqplot-table-legend-swatch');
+                    newContext.fillStyle = swatch.css('background-color');
+                    newContext.fillRect(l, t, w, h);
+                });
+
+                // now add text
+
+                $(el).find('td.jqplot-table-legend-label').each(function(){
+                    var elem = $(this);
+                    var l = left + elem.position().left;
+                    var t = top + elem.position().top + parseInt(elem.css('padding-top'), 10);
+                    newContext.font = elem.jqplotGetComputedFontStyle();
+                    newContext.fillStyle = elem.css('color');
+                    writeWrappedText(elem, newContext, elem.text(), l, t, w);
+                });
+
+                var elem = null;
+            }
+
+            else if (tagname == 'canvas') {
+                newContext.drawImage(el, left, top);
+            }
+        }
+        $(this).children().each(function() {
+            _jqpToImage(this, x_offset, y_offset);
+        });
+        return newCanvas;
+    };
+
+    // return the raw image data string.
+    // Should work on canvas supporting browsers.
+    $.fn.jqplotToImageStr = function(options) {
+        var imgCanvas = $(this).jqplotToImageCanvas(options);
+        if (imgCanvas) {
+            return imgCanvas.toDataURL("image/png");
+        }
+        else {
+            return null;
+        }
+    };
+
+    // return a DOM <img> element and return it.
+    // Should work on canvas supporting browsers.
+    $.fn.jqplotToImageElem = function(options) {
+        var elem = document.createElement("img");
+        var str = $(this).jqplotToImageStr(options);
+        elem.src = str;
+        return elem;
+    };
+
+    // return a string for an <img> element and return it.
+    // Should work on canvas supporting browsers.
+    $.fn.jqplotToImageElemStr = function(options) {
+        var str = '<img src='+$(this).jqplotToImageStr(options)+' />';
+        return str;
+    };
+
+    // Not guaranteed to work, even on canvas supporting browsers due to 
+    // limitations with location.href and browser support.
+    $.fn.jqplotSaveImage = function() {
+        var imgData = $(this).jqplotToImageStr({});
+        if (imgData) {
+            window.location.href = imgData.replace("image/png", "image/octet-stream");
+        }
+
+    };
+
+    // Not guaranteed to work, even on canvas supporting browsers due to
+    // limitations with window.open and arbitrary data.
+    $.fn.jqplotViewImage = function() {
+        var imgStr = $(this).jqplotToImageElemStr({});
+        var imgData = $(this).jqplotToImageStr({});
+        if (imgStr) {
+            var w = window.open('');
+            w.document.open("image/png");
+            w.document.write(imgStr);
+            w.document.close();
+            w = null;
+        }
+    };
+    
+
+
+
+    /** 
+     * @description
+     * <p>Object with extended date parsing and formatting capabilities.
+     * This library borrows many concepts and ideas from the Date Instance 
+     * Methods by Ken Snyder along with some parts of Ken's actual code.</p>
+     *
+     * <p>jsDate takes a different approach by not extending the built-in 
+     * Date Object, improving date parsing, allowing for multiple formatting 
+     * syntaxes and multiple and more easily expandable localization.</p>
+     * 
+     * @author Chris Leonello
+     * @date #date#
+     * @version #VERSION#
+     * @copyright (c) 2010-2013 Chris Leonello
+     * jsDate is currently available for use in all personal or commercial projects 
+     * under both the MIT and GPL version 2.0 licenses. This means that you can 
+     * choose the license that best suits your project and use it accordingly.
+     * 
+     * <p>Ken's original Date Instance Methods and copyright notice:</p>
+     * <pre>
+     * Ken Snyder (ken d snyder at gmail dot com)
+     * 2008-09-10
+     * version 2.0.2 (http://kendsnyder.com/sandbox/date/)     
+     * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
+     * </pre>
+     * 
+     * @class
+     * @name jsDate
+     * @param  {String | Number | Array | Date&nbsp;Object | Options&nbsp;Object} arguments Optional arguments, either a parsable date/time string,
+     * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds],
+     * a Date object, or an options object of form {syntax: "perl", date:some Date} where all options are optional.
+     */
+     
+    var jsDate = function () {
+    
+        this.syntax = jsDate.config.syntax;
+        this._type = "jsDate";
+        this.proxy = new Date();
+        this.options = {};
+        this.locale = jsDate.regional.getLocale();
+        this.formatString = '';
+        this.defaultCentury = jsDate.config.defaultCentury;
+
+        switch ( arguments.length ) {
+            case 0:
+                break;
+            case 1:
+                // other objects either won't have a _type property or,
+                // if they do, it shouldn't be set to "jsDate", so
+                // assume it is an options argument.
+                if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") {
+                    var opts = this.options = arguments[0];
+                    this.syntax = opts.syntax || this.syntax;
+                    this.defaultCentury = opts.defaultCentury || this.defaultCentury;
+                    this.proxy = jsDate.createDate(opts.date);
+                }
+                else {
+                    this.proxy = jsDate.createDate(arguments[0]);
+                }
+                break;
+            default:
+                var a = [];
+                for ( var i=0; i<arguments.length; i++ ) {
+                    a.push(arguments[i]);
+                }
+                // this should be the current date/time?
+                this.proxy = new Date();
+                this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) );
+                if ( a.slice(3).length ) {
+                    this.proxy.setHours.apply( this.proxy, a.slice(3) );
+                }
+                break;
+        }
+    };
+    
+    /**
+     * @namespace Configuration options that will be used as defaults for all instances on the page.
+     * @property {String} defaultLocale The default locale to use [en].
+     * @property {String} syntax The default syntax to use [perl].
+     * @property {Number} defaultCentury The default centry for 2 digit dates.
+     */
+    jsDate.config = {
+        defaultLocale: 'en',
+        syntax: 'perl',
+        defaultCentury: 1900
+    };
+        
+    /**
+     * Add an arbitrary amount to the currently stored date
+     * 
+     * @param {Number} number      
+     * @param {String} unit
+     * @returns {jsDate}       
+     */
+     
+    jsDate.prototype.add = function(number, unit) {
+        var factor = multipliers[unit] || multipliers.day;
+        if (typeof factor == 'number') {
+            this.proxy.setTime(this.proxy.getTime() + (factor * number));
+        } else {
+            factor.add(this, number);
+        }
+        return this;
+    };
+        
+    /**
+     * Create a new jqplot.date object with the same date
+     * 
+     * @returns {jsDate}
+     */  
+     
+    jsDate.prototype.clone = function() {
+            return new jsDate(this.proxy.getTime());
+    };
+
+    /**
+     * Get the UTC TimeZone Offset of this date in milliseconds.
+     *
+     * @returns {Number}
+     */
+
+    jsDate.prototype.getUtcOffset = function() {
+        return this.proxy.getTimezoneOffset() * 60000;
+    };
+
+    /**
+     * Find the difference between this jsDate and another date.
+     * 
+     * @param {String| Number| Array| jsDate&nbsp;Object| Date&nbsp;Object} dateObj
+     * @param {String} unit
+     * @param {Boolean} allowDecimal
+     * @returns {Number} Number of units difference between dates.
+     */
+     
+    jsDate.prototype.diff = function(dateObj, unit, allowDecimal) {
+        // ensure we have a Date object
+        dateObj = new jsDate(dateObj);
+        if (dateObj === null) {
+            return null;
+        }
+        // get the multiplying factor integer or factor function
+        var factor = multipliers[unit] || multipliers.day;
+        if (typeof factor == 'number') {
+            // multiply
+            var unitDiff = (this.proxy.getTime() - dateObj.proxy.getTime()) / factor;
+        } else {
+            // run function
+            var unitDiff = factor.diff(this.proxy, dateObj.proxy);
+        }
+        // if decimals are not allowed, round toward zero
+        return (allowDecimal ? unitDiff : Math[unitDiff > 0 ? 'floor' : 'ceil'](unitDiff));          
+    };
+    
+    /**
+     * Get the abbreviated name of the current week day
+     * 
+     * @returns {String}
+     */   
+     
+    jsDate.prototype.getAbbrDayName = function() {
+        return jsDate.regional[this.locale]["dayNamesShort"][this.proxy.getDay()];
+    };
+    
+    /**
+     * Get the abbreviated name of the current month
+     * 
+     * @returns {String}
+     */
+     
+    jsDate.prototype.getAbbrMonthName = function() {
+        return jsDate.regional[this.locale]["monthNamesShort"][this.proxy.getMonth()];
+    };
+    
+    /**
+     * Get UPPER CASE AM or PM for the current time
+     * 
+     * @returns {String}
+     */
+     
+    jsDate.prototype.getAMPM = function() {
+        return this.proxy.getHours() >= 12 ? 'PM' : 'AM';
+    };
+    
+    /**
+     * Get lower case am or pm for the current time
+     * 
+     * @returns {String}
+     */
+     
+    jsDate.prototype.getAmPm = function() {
+        return this.proxy.getHours() >= 12 ? 'pm' : 'am';
+    };
+    
+    /**
+     * Get the century (19 for 20th Century)
+     *
+     * @returns {Integer} Century (19 for 20th century).
+     */
+    jsDate.prototype.getCentury = function() { 
+        return parseInt(this.proxy.getFullYear()/100, 10);
+    };
+    
+    /**
+     * Implements Date functionality
+     */
+    jsDate.prototype.getDate = function() {
+        return this.proxy.getDate();
+    };
+    
+    /**
+     * Implements Date functionality
+     */
+    jsDate.prototype.getDay = function() {
+        return this.proxy.getDay();
+    };
+    
+    /**
+     * Get the Day of week 1 (Monday) thru 7 (Sunday)
+     * 
+     * @returns {Integer} Day of week 1 (Monday) thru 7 (Sunday)
+     */
+    jsDate.prototype.getDayOfWeek = function() { 
+        var dow = this.proxy.getDay(); 
+        return dow===0?7:dow; 
+    };
+    
+    /**
+     * Get the day of the year
+     * 
+     * @returns {Integer} 1 - 366, day of the year
+     */
+    jsDate.prototype.getDayOfYear = function() {
+        var d = this.proxy;
+        var ms = d - new Date('' + d.getFullYear() + '/1/1 GMT');
+        ms += d.getTimezoneOffset()*60000;
+        d = null;
+        return parseInt(ms/60000/60/24, 10)+1;
+    };
+    
+    /**
+     * Get the name of the current week day
+     * 
+     * @returns {String}
+     */  
+     
+    jsDate.prototype.getDayName = function() {
+        return jsDate.regional[this.locale]["dayNames"][this.proxy.getDay()];
+    };
+    
+    /**
+     * Get the week number of the given year, starting with the first Sunday as the first week
+     * @returns {Integer} Week number (13 for the 13th full week of the year).
+     */
+    jsDate.prototype.getFullWeekOfYear = function() {
+        var d = this.proxy;
+        var doy = this.getDayOfYear();
+        var rdow = 6-d.getDay();
+        var woy = parseInt((doy+rdow)/7, 10);
+        return woy;
+    };
+    
+    /**
+     * Implements Date functionality
+     */
+    jsDate.prototype.getFullYear = function() {
+        return this.proxy.getFullYear();
+    };
+    
+    /**
+     * Get the GMT offset in hours and minutes (e.g. +06:30)
+     * 
+     * @returns {String}
+     */
+     
+    jsDate.prototype.getGmtOffset = function() {
+        // divide the minutes offset by 60
+        var hours = this.proxy.getTimezoneOffset() / 60;
+        // decide if we are ahead of or behind GMT
+        var prefix = hours < 0 ? '+' : '-';
+        // remove the negative sign if any
+        hours = Math.abs(hours);
+        // add the +/- to the padded number of hours to : to the padded minutes
+        return prefix + addZeros(Math.floor(hours), 2) + ':' + addZeros((hours % 1) * 60, 2);
+    };
+    
+    /**
+     * Implements Date functionality
+     */
+    jsDate.prototype.getHours = function() {
+        return this.proxy.getHours();
+    };
+    
+    /**
+     * Get the current hour on a 12-hour scheme
+     * 
+     * @returns {Integer}
+     */
+     
+    jsDate.prototype.getHours12  = function() {
+        var hours = this.proxy.getHours();
+        return hours > 12 ? hours - 12 : (hours == 0 ? 12 : hours);
+    };
+    
+    
+    jsDate.prototype.getIsoWeek = function() {
+        var d = this.proxy;
+        var woy = this.getWeekOfYear();
+        var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay();
+        // First week is 01 and not 00 as in the case of %U and %W,
+        // so we add 1 to the final result except if day 1 of the year
+        // is a Monday (then %W returns 01).
+        // We also need to subtract 1 if the day 1 of the year is 
+        // Friday-Sunday, so the resulting equation becomes:
+        var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1);
+        if(idow == 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4)
+        {
+            idow = 1;
+        }
+        else if(idow === 0)
+        {
+            d = new jsDate(new Date('' + (d.getFullYear()-1) + '/12/31'));
+            idow = d.getIsoWeek();
+        }
+        d = null;
+        return idow;
+    };
+    
+    /**
+     * Implements Date functionality
+     */
+    jsDate.prototype.getMilliseconds = function() {
+        return this.proxy.getMilliseconds();
+    };
+    
+    /**
+     * Implements Date functionality
+     */
+    jsDate.prototype.getMinutes = function() {
+        return this.proxy.getMinutes();
+    };
+    
+    /**
+     * Implements Date functionality
+     */
+    jsDate.prototype.getMonth = function() {
+        return this.proxy.getMonth();
+    };
+    
+    /**
+     * Get the name of the current month
+     * 
+     * @returns {String}
+     */
+     
+    jsDate.prototype.getMonthName = function() {
+        return jsDate.regional[this.locale]["monthNames"][this.proxy.getMonth()];
+    };
+    
+    /**
+     * Get the number of the current month, 1-12
+     * 
+     * @returns {Integer}
+     */
+     
+    jsDate.prototype.getMonthNumber = function() {
+        return this.proxy.getMonth() + 1;
+    };
+    
+    /**
+     * Implements Date functionality
+     */
+    jsDate.prototype.getSeconds = function() {
+        return this.proxy.getSeconds();
+    };
+    
+    /**
+     * Return a proper two-digit year integer
+     * 
+     * @returns {Integer}
+     */
+     
+    jsDate.prototype.getShortYear = function() {
+        return this.proxy.getYear() % 100;
+    };
+    
+    /**
+     * Implements Date functionality
+     */
+    jsDate.prototype.getTime = function() {
+        return this.proxy.getTime();
+    };
+    
+    /**
+     * Get the timezone abbreviation
+     *
+     * @returns {String} Abbreviation for the timezone
+     */
+    jsDate.prototype.getTimezoneAbbr = function() {
+        return this.proxy.toString().replace(/^.*\(([^)]+)\)$/, '$1'); 
+    };
+    
+    /**
+     * Get the browser-reported name for the current timezone (e.g. MDT, Mountain Daylight Time)
+     * 
+     * @returns {String}
+     */
+    jsDate.prototype.getTimezoneName = function() {
+        var match = /(?:\((.+)\)$| ([A-Z]{3}) )/.exec(this.toString());
+        return match[1] || match[2] || 'GMT' + this.getGmtOffset();
+    }; 
+    
+    /**
+     * Implements Date functionality
+     */
+    jsDate.prototype.getTimezoneOffset = function() {
+        return this.proxy.getTimezoneOffset();
+    };
+    
+    
+    /**
+     * Get the week number of the given year, starting with the first Monday as the first week
+     * @returns {Integer} Week number (13 for the 13th week of the year).
+     */
+    jsDate.prototype.getWeekOfYear = function() {
+        var doy = this.getDayOfYear();
+        var rdow = 7 - this.getDayOfWeek();
+        var woy = parseInt((doy+rdow)/7, 10);
+        return woy;
+    };
+    
+    /**
+     * Get the current date as a Unix timestamp
+     * 
+     * @returns {Integer}
+     */
+     
+    jsDate.prototype.getUnix = function() {
+        return Math.round(this.proxy.getTime() / 1000, 0);
+    }; 
+    
+    /**
+     * Implements Date functionality
+     */
+    jsDate.prototype.getYear = function() {
+        return this.proxy.getYear();
+    };
+    
+    /**
+     * Return a date one day ahead (or any other unit)
+     * 
+     * @param {String} unit Optional, year | month | day | week | hour | minute | second | millisecond
+     * @returns {jsDate}
+     */
+     
+    jsDate.prototype.next = function(unit) {
+        unit = unit || 'day';
+        return this.clone().add(1, unit);
+    };
+    
+    /**
+     * Set the jsDate instance to a new date.
+     *
+     * @param  {String | Number | Array | Date Object | jsDate Object | Options Object} arguments Optional arguments, 
+     * either a parsable date/time string,
+     * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds],
+     * a Date object, jsDate Object or an options object of form {syntax: "perl", date:some Date} where all options are optional.
+     */
+    jsDate.prototype.set = function() {
+        switch ( arguments.length ) {
+            case 0:
+                this.proxy = new Date();
+                break;
+            case 1:
+                // other objects either won't have a _type property or,
+                // if they do, it shouldn't be set to "jsDate", so
+                // assume it is an options argument.
+                if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") {
+                    var opts = this.options = arguments[0];
+                    this.syntax = opts.syntax || this.syntax;
+                    this.defaultCentury = opts.defaultCentury || this.defaultCentury;
+                    this.proxy = jsDate.createDate(opts.date);
+                }
+                else {
+                    this.proxy = jsDate.createDate(arguments[0]);
+                }
+                break;
+            default:
+                var a = [];
+                for ( var i=0; i<arguments.length; i++ ) {
+                    a.push(arguments[i]);
+                }
+                // this should be the current date/time
+                this.proxy = new Date();
+                this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) );
+                if ( a.slice(3).length ) {
+                    this.proxy.setHours.apply( this.proxy, a.slice(3) );
+                }
+                break;
+        }
+        return this;
+    };
+    
+    /**
+     * Sets the day of the month for a specified date according to local time.
+     * @param {Integer} dayValue An integer from 1 to 31, representing the day of the month. 
+     */
+    jsDate.prototype.setDate = function(n) {
+        this.proxy.setDate(n);
+        return this;
+    };
+    
+    /**
+     * Sets the full year for a specified date according to local time.
+     * @param {Integer} yearValue The numeric value of the year, for example, 1995.  
+     * @param {Integer} monthValue Optional, between 0 and 11 representing the months January through December.  
+     * @param {Integer} dayValue Optional, between 1 and 31 representing the day of the month. If you specify the dayValue parameter, you must also specify the monthValue. 
+     */
+    jsDate.prototype.setFullYear = function() {
+        this.proxy.setFullYear.apply(this.proxy, arguments);
+        return this;
+    };
+    
+    /**
+     * Sets the hours for a specified date according to local time.
+     * 
+     * @param {Integer} hoursValue An integer between 0 and 23, representing the hour.  
+     * @param {Integer} minutesValue Optional, An integer between 0 and 59, representing the minutes.  
+     * @param {Integer} secondsValue Optional, An integer between 0 and 59, representing the seconds. 
+     * If you specify the secondsValue parameter, you must also specify the minutesValue.  
+     * @param {Integer} msValue Optional, A number between 0 and 999, representing the milliseconds. 
+     * If you specify the msValue parameter, you must also specify the minutesValue and secondsValue. 
+     */
+    jsDate.prototype.setHours = function() {
+        this.proxy.setHours.apply(this.proxy, arguments);
+        return this;
+    };
+    
+    /**
+     * Implements Date functionality
+     */ 
+    jsDate.prototype.setMilliseconds = function(n) {
+        this.proxy.setMilliseconds(n);
+        return this;
+    };
+    
+    /**
+     * Implements Date functionality
+     */ 
+    jsDate.prototype.setMinutes = function() {
+        this.proxy.setMinutes.apply(this.proxy, arguments);
+        return this;
+    };
+    
+    /**
+     * Implements Date functionality
+     */ 
+    jsDate.prototype.setMonth = function() {
+        this.proxy.setMonth.apply(this.proxy, arguments);
+        return this;
+    };
+    
+    /**
+     * Implements Date functionality
+     */ 
+    jsDate.prototype.setSeconds = function() {
+        this.proxy.setSeconds.apply(this.proxy, arguments);
+        return this;
+    };
+    
+    /**
+     * Implements Date functionality
+     */ 
+    jsDate.prototype.setTime = function(n) {
+        this.proxy.setTime(n);
+        return this;
+    };
+    
+    /**
+     * Implements Date functionality
+     */ 
+    jsDate.prototype.setYear = function() {
+        this.proxy.setYear.apply(this.proxy, arguments);
+        return this;
+    };
+    
+    /**
+     * Provide a formatted string representation of this date.
+     * 
+     * @param {String} formatString A format string.  
+     * See: {@link jsDate.formats}.
+     * @returns {String} Date String.
+     */
+            
+    jsDate.prototype.strftime = function(formatString) {
+        formatString = formatString || this.formatString || jsDate.regional[this.locale]['formatString'];
+        return jsDate.strftime(this, formatString, this.syntax);
+    };
+        
+    /**
+     * Return a String representation of this jsDate object.
+     * @returns {String} Date string.
+     */
+    
+    jsDate.prototype.toString = function() {
+        return this.proxy.toString();
+    };
+        
+    /**
+     * Convert the current date to an 8-digit integer (%Y%m%d)
+     * 
+     * @returns {Integer}
+     */
+     
+    jsDate.prototype.toYmdInt = function() {
+        return (this.proxy.getFullYear() * 10000) + (this.getMonthNumber() * 100) + this.proxy.getDate();
+    };
+    
+    /**
+     * @namespace Holds localizations for month/day names.
+     * <p>jsDate attempts to detect locale when loaded and defaults to 'en'.
+     * If a localization is detected which is not available, jsDate defaults to 'en'.
+     * Additional localizations can be added after jsDate loads.  After adding a localization,
+     * call the jsDate.regional.getLocale() method.  Currently, en, fr and de are defined.</p>
+     * 
+     * <p>Localizations must be an object and have the following properties defined:  monthNames, monthNamesShort, dayNames, dayNamesShort and Localizations are added like:</p>
+     * <pre class="code">
+     * jsDate.regional['en'] = {
+     * monthNames      : 'January February March April May June July August September October November December'.split(' '),
+     * monthNamesShort : 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '),
+     * dayNames        : 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' '),
+     * dayNamesShort   : 'Sun Mon Tue Wed Thu Fri Sat'.split(' ')
+     * };
+     * </pre>
+     * <p>After adding localizations, call <code>jsDate.regional.getLocale();</code> to update the locale setting with the
+     * new localizations.</p>
+     */
+     
+    jsDate.regional = {
+        'en': {
+            monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
+            monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun','Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+            dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+            dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+            formatString: '%Y-%m-%d %H:%M:%S'
+        },
+        
+        'fr': {
+            monthNames: ['Janvier','FÃ©vrier','Mars','Avril','Mai','Juin','Juillet','AoÃ»t','Septembre','Octobre','Novembre','DÃ©cembre'],
+            monthNamesShort: ['Jan','FÃ©v','Mar','Avr','Mai','Jun','Jul','AoÃ»','Sep','Oct','Nov','DÃ©c'],
+            dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
+            dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'],
+            formatString: '%Y-%m-%d %H:%M:%S'
+        },
+        
+        'de': {
+            monthNames: ['Januar','Februar','MÃ€rz','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],
+            monthNamesShort: ['Jan','Feb','MÃ€r','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'],
+            dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'],
+            dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'],
+            formatString: '%Y-%m-%d %H:%M:%S'
+        },
+        
+        'es': {
+            monthNames: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'],
+            monthNamesShort: ['Ene','Feb','Mar','Abr','May','Jun', 'Jul','Ago','Sep','Oct','Nov','Dic'],
+            dayNames: ['Domingo','Lunes','Martes','Mi&eacute;rcoles','Jueves','Viernes','S&aacute;bado'],
+            dayNamesShort: ['Dom','Lun','Mar','Mi&eacute;','Juv','Vie','S&aacute;b'],
+            formatString: '%Y-%m-%d %H:%M:%S'
+        },
+        
+        'ru': {
+            monthNames: ['Ð¯ÐœÐ²Ð°ÑÑ','Ð€ÐµÐ²ÑÐ°Ð»Ñ','ÐÐ°ÑÑ','ÐÐ¿ÑÐµÐ»Ñ','ÐÐ°Ð¹','ÐÑÐœÑ','ÐÑÐ»Ñ','ÐÐ²Ð³ÑÑÑ','Ð¡ÐµÐœÑÑÐ±ÑÑ','ÐÐºÑÑÐ±ÑÑ','ÐÐŸÑÐ±ÑÑ','ÐÐµÐºÐ°Ð±ÑÑ'],
+            monthNamesShort: ['Ð¯ÐœÐ²','Ð€ÐµÐ²','ÐÐ°Ñ','ÐÐ¿Ñ','ÐÐ°Ð¹','ÐÑÐœ','ÐÑÐ»','ÐÐ²Ð³','Ð¡ÐµÐœ','ÐÐºÑ','ÐÐŸÑ','ÐÐµÐº'],
+            dayNames: ['Ð²ÐŸÑÐºÑÐµÑÐµÐœÑÐµ','Ð¿ÐŸÐœÐµÐŽÐµÐ»ÑÐœÐžÐº','Ð²ÑÐŸÑÐœÐžÐº','ÑÑÐµÐŽÐ°','ÑÐµÑÐ²ÐµÑÐ³','Ð¿ÑÑÐœÐžÑÐ°','ÑÑÐ±Ð±ÐŸÑÐ°'],
+            dayNamesShort: ['Ð²ÑÐº','Ð¿ÐœÐŽ','Ð²ÑÑ','ÑÑÐŽ','ÑÑÐ²','Ð¿ÑÐœ','ÑÐ±Ñ'],
+            formatString: '%Y-%m-%d %H:%M:%S'
+        },
+        
+        'ar': {
+            monthNames: ['ÙØ§ÙÙÙ Ø§ÙØ«Ø§ÙÙ', 'ØŽØšØ§Ø·', 'Ø¢Ø°Ø§Ø±', 'ÙÙØ³Ø§Ù', 'Ø¢Ø°Ø§Ø±', 'Ø­Ø²ÙØ±Ø§Ù','ØªÙ
+ÙØ²', 'Ø¢Øš', 'Ø£ÙÙÙÙ',   'ØªØŽØ±ÙÙ Ø§ÙØ£ÙÙ', 'ØªØŽØ±ÙÙ Ø§ÙØ«Ø§ÙÙ', 'ÙØ§ÙÙÙ Ø§ÙØ£ÙÙ'],
+            monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'],
+            dayNames: ['Ø§ÙØ³ØšØª', 'Ø§ÙØ£Ø­Ø¯', 'Ø§ÙØ§Ø«ÙÙÙ', 'Ø§ÙØ«ÙØ§Ø«Ø§Ø¡', 'Ø§ÙØ£Ø±ØšØ¹Ø§Ø¡', 'Ø§ÙØ®Ù
+ÙØ³', 'Ø§ÙØ¬Ù
+Ø¹Ø©'],
+            dayNamesShort: ['Ø³ØšØª', 'Ø£Ø­Ø¯', 'Ø§Ø«ÙÙÙ', 'Ø«ÙØ§Ø«Ø§Ø¡', 'Ø£Ø±ØšØ¹Ø§Ø¡', 'Ø®Ù
+ÙØ³', 'Ø¬Ù
+Ø¹Ø©'],
+            formatString: '%Y-%m-%d %H:%M:%S'
+        },
+        
+        'pt': {
+            monthNames: ['Janeiro','Fevereiro','Mar&ccedil;o','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
+            monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
+            dayNames: ['Domingo','Segunda-feira','Ter&ccedil;a-feira','Quarta-feira','Quinta-feira','Sexta-feira','S&aacute;bado'],
+            dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','S&aacute;b'],
+            formatString: '%Y-%m-%d %H:%M:%S'   
+        },
+        
+        'pt-BR': {
+            monthNames: ['Janeiro','Fevereiro','Mar&ccedil;o','Abril','Maio','Junho', 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
+            monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
+            dayNames: ['Domingo','Segunda-feira','Ter&ccedil;a-feira','Quarta-feira','Quinta-feira','Sexta-feira','S&aacute;bado'],
+            dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','S&aacute;b'],
+            formatString: '%Y-%m-%d %H:%M:%S'
+        },
+        
+        'pl': {
+            monthNames: ['StyczeÅ','Luty','Marzec','KwiecieÅ','Maj','Czerwiec','Lipiec','SierpieÅ','WrzesieÅ','PaÅºdziernik','Listopad','GrudzieÅ'],
+            monthNamesShort: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze','Lip', 'Sie', 'Wrz', 'PaÅº', 'Lis', 'Gru'],
+            dayNames: ['Niedziela', 'PoniedziaÅek', 'Wtorek', 'Åroda', 'Czwartek', 'PiÄ
+tek', 'Sobota'],
+            dayNamesShort: ['Ni', 'Pn', 'Wt', 'År', 'Cz', 'Pt', 'Sb'],
+            formatString: '%Y-%m-%d %H:%M:%S'
+        },
+
+        'nl': {
+            monthNames: ['Januari','Februari','Maart','April','Mei','Juni','July','Augustus','September','Oktober','November','December'],
+            monthNamesShort: ['Jan','Feb','Mar','Apr','Mei','Jun','Jul','Aug','Sep','Okt','Nov','Dec'],
+            dayNames:','['Zondag','Maandag','Dinsdag','Woensdag','Donderdag','Vrijdag','Zaterdag'],
+            dayNamesShort: ['Zo','Ma','Di','Wo','Do','Vr','Za'],
+            formatString: '%Y-%m-%d %H:%M:%S'
+        },
+
+        'sv': {
+            monthNames: ['januari','februari','mars','april','maj','juni','juli','augusti','september','oktober','november','december'],
+          monthNamesShort: ['jan','feb','mar','apr','maj','jun','jul','aug','sep','okt','nov','dec'],
+            dayNames: ['sÃ¶ndag','mÃ¥ndag','tisdag','onsdag','torsdag','fredag','lÃ¶rdag'],
+            dayNamesShort: ['sÃ¶n','mÃ¥n','tis','ons','tor','fre','lÃ¶r'],
+            formatString: '%Y-%m-%d %H:%M:%S'
+        }
+    
+    };
+    
+    // Set english variants to 'en'
+    jsDate.regional['en-US'] = jsDate.regional['en-GB'] = jsDate.regional['en'];
+    
+    /**
+     * Try to determine the users locale based on the lang attribute of the html page.  Defaults to 'en'
+     * if it cannot figure out a locale of if the locale does not have a localization defined.
+     * @returns {String} locale
+     */
+     
+    jsDate.regional.getLocale = function () {
+        var l = jsDate.config.defaultLocale;
+        
+        if ( document && document.getElementsByTagName('html') && document.getElementsByTagName('html')[0].lang ) {
+            l = document.getElementsByTagName('html')[0].lang;
+            if (!jsDate.regional.hasOwnProperty(l)) {
+                l = jsDate.config.defaultLocale;
+            }
+        }
+        
+        return l;
+    };
+    
+    // ms in day
+    var day = 24 * 60 * 60 * 1000;
+    
+    // padd a number with zeros
+    var addZeros = function(num, digits) {
+        num = String(num);
+        var i = digits - num.length;
+        var s = String(Math.pow(10, i)).slice(1);
+        return s.concat(num);
+    };
+
+    // representations used for calculating differences between dates.
+    // This borrows heavily from Ken Snyder's work.
+    var multipliers = {
+        millisecond: 1,
+        second: 1000,
+        minute: 60 * 1000,
+        hour: 60 * 60 * 1000,
+        day: day,
+        week: 7 * day,
+        month: {
+            // add a number of months
+            add: function(d, number) {
+                // add any years needed (increments of 12)
+                multipliers.year.add(d, Math[number > 0 ? 'floor' : 'ceil'](number / 12));
+                // ensure that we properly wrap betwen December and January
+                // 11 % 12 = 11
+                // 12 % 12 = 0
+                var prevMonth = d.getMonth() + (number % 12);
+                if (prevMonth == 12) {
+                    prevMonth = 0;
+                    d.setYear(d.getFullYear() + 1);
+                } else if (prevMonth == -1) {
+                    prevMonth = 11;
+                    d.setYear(d.getFullYear() - 1);
+                }
+                d.setMonth(prevMonth);
+            },
+            // get the number of months between two Date objects (decimal to the nearest day)
+            diff: function(d1, d2) {
+                // get the number of years
+                var diffYears = d1.getFullYear() - d2.getFullYear();
+                // get the number of remaining months
+                var diffMonths = d1.getMonth() - d2.getMonth() + (diffYears * 12);
+                // get the number of remaining days
+                var diffDays = d1.getDate() - d2.getDate();
+                // return the month difference with the days difference as a decimal
+                return diffMonths + (diffDays / 30);
+            }
+        },
+        year: {
+            // add a number of years
+            add: function(d, number) {
+                d.setYear(d.getFullYear() + Math[number > 0 ? 'floor' : 'ceil'](number));
+            },
+            // get the number of years between two Date objects (decimal to the nearest day)
+            diff: function(d1, d2) {
+                return multipliers.month.diff(d1, d2) / 12;
+            }
+        }        
+    };
+    //
+    // Alias each multiplier with an 's' to allow 'year' and 'years' for example.
+    // This comes from Ken Snyders work.
+    //
+    for (var unit in multipliers) {
+        if (unit.substring(unit.length - 1) != 's') { // IE will iterate newly added properties :|
+            multipliers[unit + 's'] = multipliers[unit];
+        }
+    }
+    
+    //
+    // take a jsDate instance and a format code and return the formatted value.
+    // This is a somewhat modified version of Ken Snyder's method.
+    //
+    var format = function(d, code, syntax) {
+        // if shorcut codes are used, recursively expand those.
+        if (jsDate.formats[syntax]["shortcuts"][code]) {
+            return jsDate.strftime(d, jsDate.formats[syntax]["shortcuts"][code], syntax);
+        } else {
+            // get the format code function and addZeros() argument
+            var getter = (jsDate.formats[syntax]["codes"][code] || '').split('.');
+            var nbr = d['get' + getter[0]] ? d['get' + getter[0]]() : '';
+            if (getter[1]) {
+                nbr = addZeros(nbr, getter[1]);
+            }
+            return nbr;
+        }       
+    };
+    
+    /**
+     * @static
+     * Static function for convert a date to a string according to a given format.  Also acts as namespace for strftime format codes.
+     * <p>strftime formatting can be accomplished without creating a jsDate object by calling jsDate.strftime():</p>
+     * <pre class="code">
+     * var formattedDate = jsDate.strftime('Feb 8, 2006 8:48:32', '%Y-%m-%d %H:%M:%S');
+     * </pre>
+     * @param {String | Number | Array | jsDate&nbsp;Object | Date&nbsp;Object} date A parsable date string, JavaScript time stamp, Array of form [year, month, day, hours, minutes, seconds, milliseconds], jsDate Object or Date object.
+     * @param {String} formatString String with embedded date formatting codes.  
+     * See: {@link jsDate.formats}. 
+     * @param {String} syntax Optional syntax to use [default perl].
+     * @param {String} locale Optional locale to use.
+     * @returns {String} Formatted representation of the date.
+    */
+    //
+    // Logic as implemented here is very similar to Ken Snyder's Date Instance Methods.
+    //
+    jsDate.strftime = function(d, formatString, syntax, locale) {
+        var syn = 'perl';
+        var loc = jsDate.regional.getLocale();
+        
+        // check if syntax and locale are available or reversed
+        if (syntax && jsDate.formats.hasOwnProperty(syntax)) {
+            syn = syntax;
+        }
+        else if (syntax && jsDate.regional.hasOwnProperty(syntax)) {
+            loc = syntax;
+        }
+        
+        if (locale && jsDate.formats.hasOwnProperty(locale)) {
+            syn = locale;
+        }
+        else if (locale && jsDate.regional.hasOwnProperty(locale)) {
+            loc = locale;
+        }
+        
+        if (get_type(d) != "[object Object]" || d._type != "jsDate") {
+            d = new jsDate(d);
+            d.locale = loc;
+        }
+        if (!formatString) {
+            formatString = d.formatString || jsDate.regional[loc]['formatString'];
+        }
+        // default the format string to year-month-day
+        var source = formatString || '%Y-%m-%d', 
+            result = '', 
+            match;
+        // replace each format code
+        while (source.length > 0) {
+            if (match = source.match(jsDate.formats[syn].codes.matcher)) {
+                result += source.slice(0, match.index);
+                result += (match[1] || '') + format(d, match[2], syn);
+                source = source.slice(match.index + match[0].length);
+            } else {
+                result += source;
+                source = '';
+            }
+        }
+        return result;
+    };
+    
+    /**
+     * @namespace
+     * Namespace to hold format codes and format shortcuts.  "perl" and "php" format codes 
+     * and shortcuts are defined by default.  Additional codes and shortcuts can be
+     * added like:
+     * 
+     * <pre class="code">
+     * jsDate.formats["perl"] = {
+     *     "codes": {
+     *         matcher: /someregex/,
+     *         Y: "fullYear",  // name of "get" method without the "get",
+     *         ...,            // more codes
+     *     },
+     *     "shortcuts": {
+     *         F: '%Y-%m-%d',
+     *         ...,            // more shortcuts
+     *     }
+     * };
+     * </pre>
+     * 
+     * <p>Additionally, ISO and SQL shortcuts are defined and can be accesses via:
+     * <code>jsDate.formats.ISO</code> and <code>jsDate.formats.SQL</code>
+     */
+    
+    jsDate.formats = {
+        ISO:'%Y-%m-%dT%H:%M:%S.%N%G',
+        SQL:'%Y-%m-%d %H:%M:%S'
+    };
+    
+    /**
+     * Perl format codes and shortcuts for strftime.
+     * 
+     * A hash (object) of codes where each code must be an array where the first member is 
+     * the name of a Date.prototype or jsDate.prototype function to call
+     * and optionally a second member indicating the number to pass to addZeros()
+     * 
+     * <p>The following format codes are defined:</p>
+     * 
+     * <pre class="code">
+     * Code    Result                    Description
+     * == Years ==           
+     * %Y      2008                      Four-digit year
+     * %y      08                        Two-digit year
+     * 
+     * == Months ==          
+     * %m      09                        Two-digit month
+     * %#m     9                         One or two-digit month
+     * %B      September                 Full month name
+     * %b      Sep                       Abbreviated month name
+     * 
+     * == Days ==            
+     * %d      05                        Two-digit day of month
+     * %#d     5                         One or two-digit day of month
+     * %e      5                         One or two-digit day of month
+     * %A      Sunday                    Full name of the day of the week
+     * %a      Sun                       Abbreviated name of the day of the week
+     * %w      0                         Number of the day of the week (0 = Sunday, 6 = Saturday)
+     * 
+     * == Hours ==           
+     * %H      23                        Hours in 24-hour format (two digits)
+     * %#H     3                         Hours in 24-hour integer format (one or two digits)
+     * %I      11                        Hours in 12-hour format (two digits)
+     * %#I     3                         Hours in 12-hour integer format (one or two digits)
+     * %p      PM                        AM or PM
+     * 
+     * == Minutes ==         
+     * %M      09                        Minutes (two digits)
+     * %#M     9                         Minutes (one or two digits)
+     * 
+     * == Seconds ==         
+     * %S      02                        Seconds (two digits)
+     * %#S     2                         Seconds (one or two digits)
+     * %s      1206567625723             Unix timestamp (Seconds past 1970-01-01 00:00:00)
+     * 
+     * == Milliseconds ==    
+     * %N      008                       Milliseconds (three digits)
+     * %#N     8                         Milliseconds (one to three digits)
+     * 
+     * == Timezone ==        
+     * %O      360                       difference in minutes between local time and GMT
+     * %Z      Mountain Standard Time    Name of timezone as reported by browser
+     * %G      06:00                     Hours and minutes between GMT
+     * 
+     * == Shortcuts ==       
+     * %F      2008-03-26                %Y-%m-%d
+     * %T      05:06:30                  %H:%M:%S
+     * %X      05:06:30                  %H:%M:%S
+     * %x      03/26/08                  %m/%d/%y
+     * %D      03/26/08                  %m/%d/%y
+     * %#c     Wed Mar 26 15:31:00 2008  %a %b %e %H:%M:%S %Y
+     * %v      3-Sep-2008                %e-%b-%Y
+     * %R      15:31                     %H:%M
+     * %r      03:31:00 PM               %I:%M:%S %p
+     * 
+     * == Characters ==      
+     * %n      \n                        Newline
+     * %t      \t                        Tab
+     * %%      %                         Percent Symbol
+     * </pre>
+     * 
+     * <p>Formatting shortcuts that will be translated into their longer version.
+     * Be sure that format shortcuts do not refer to themselves: this will cause an infinite loop.</p>
+     * 
+     * <p>Format codes and format shortcuts can be redefined after the jsDate
+     * module is imported.</p>
+     * 
+     * <p>Note that if you redefine the whole hash (object), you must supply a "matcher"
+     * regex for the parser.  The default matcher is:</p>
+     * 
+     * <code>/()%(#?(%|[a-z]))/i</code>
+     * 
+     * <p>which corresponds to the Perl syntax used by default.</p>
+     * 
+     * <p>By customizing the matcher and format codes, nearly any strftime functionality is possible.</p>
+     */
+     
+    jsDate.formats.perl = {
+        codes: {
+            //
+            // 2-part regex matcher for format codes
+            //
+            // first match must be the character before the code (to account for escaping)
+            // second match must be the format code character(s)
+            //
+            matcher: /()%(#?(%|[a-z]))/i,
+            // year
+            Y: 'FullYear',
+            y: 'ShortYear.2',
+            // month
+            m: 'MonthNumber.2',
+            '#m': 'MonthNumber',
+            B: 'MonthName',
+            b: 'AbbrMonthName',
+            // day
+            d: 'Date.2',
+            '#d': 'Date',
+            e: 'Date',
+            A: 'DayName',
+            a: 'AbbrDayName',
+            w: 'Day',
+            // hours
+            H: 'Hours.2',
+            '#H': 'Hours',
+            I: 'Hours12.2',
+            '#I': 'Hours12',
+            p: 'AMPM',
+            // minutes
+            M: 'Minutes.2',
+            '#M': 'Minutes',
+            // seconds
+            S: 'Seconds.2',
+            '#S': 'Seconds',
+            s: 'Unix',
+            // milliseconds
+            N: 'Milliseconds.3',
+            '#N': 'Milliseconds',
+            // timezone
+            O: 'TimezoneOffset',
+            Z: 'TimezoneName',
+            G: 'GmtOffset'  
+        },
+        
+        shortcuts: {
+            // date
+            F: '%Y-%m-%d',
+            // time
+            T: '%H:%M:%S',
+            X: '%H:%M:%S',
+            // local format date
+            x: '%m/%d/%y',
+            D: '%m/%d/%y',
+            // local format extended
+            '#c': '%a %b %e %H:%M:%S %Y',
+            // local format short
+            v: '%e-%b-%Y',
+            R: '%H:%M',
+            r: '%I:%M:%S %p',
+            // tab and newline
+            t: '\t',
+            n: '\n',
+            '%': '%'
+        }
+    };
+    
+    /**
+     * PHP format codes and shortcuts for strftime.
+     * 
+     * A hash (object) of codes where each code must be an array where the first member is 
+     * the name of a Date.prototype or jsDate.prototype function to call
+     * and optionally a second member indicating the number to pass to addZeros()
+     * 
+     * <p>The following format codes are defined:</p>
+     * 
+     * <pre class="code">
+     * Code    Result                    Description
+     * === Days ===        
+     * %a      Sun through Sat           An abbreviated textual representation of the day
+     * %A      Sunday - Saturday         A full textual representation of the day
+     * %d      01 to 31                  Two-digit day of the month (with leading zeros)
+     * %e      1 to 31                   Day of the month, with a space preceding single digits.
+     * %j      001 to 366                Day of the year, 3 digits with leading zeros
+     * %u      1 - 7 (Mon - Sun)         ISO-8601 numeric representation of the day of the week
+     * %w      0 - 6 (Sun - Sat)         Numeric representation of the day of the week
+     *                                  
+     * === Week ===                     
+     * %U      13                        Full Week number, starting with the first Sunday as the first week
+     * %V      01 through 53             ISO-8601:1988 week number, starting with the first week of the year 
+     *                                   with at least 4 weekdays, with Monday being the start of the week
+     * %W      46                        A numeric representation of the week of the year, 
+     *                                   starting with the first Monday as the first week
+     * === Month ===                    
+     * %b      Jan through Dec           Abbreviated month name, based on the locale
+     * %B      January - December        Full month name, based on the locale
+     * %h      Jan through Dec           Abbreviated month name, based on the locale (an alias of %b)
+     * %m      01 - 12 (Jan - Dec)       Two digit representation of the month
+     * 
+     * === Year ===                     
+     * %C      19                        Two digit century (year/100, truncated to an integer)
+     * %y      09 for 2009               Two digit year
+     * %Y      2038                      Four digit year
+     * 
+     * === Time ===                     
+     * %H      00 through 23             Two digit representation of the hour in 24-hour format
+     * %I      01 through 12             Two digit representation of the hour in 12-hour format
+     * %l      1 through 12              Hour in 12-hour format, with a space preceeding single digits
+     * %M      00 through 59             Two digit representation of the minute
+     * %p      AM/PM                     UPPER-CASE 'AM' or 'PM' based on the given time
+     * %P      am/pm                     lower-case 'am' or 'pm' based on the given time
+     * %r      09:34:17 PM               Same as %I:%M:%S %p
+     * %R      00:35                     Same as %H:%M
+     * %S      00 through 59             Two digit representation of the second
+     * %T      21:34:17                  Same as %H:%M:%S
+     * %X      03:59:16                  Preferred time representation based on locale, without the date
+     * %z      -0500 or EST              Either the time zone offset from UTC or the abbreviation
+     * %Z      -0500 or EST              The time zone offset/abbreviation option NOT given by %z
+     * 
+     * === Time and Date ===            
+     * %D      02/05/09                  Same as %m/%d/%y
+     * %F      2009-02-05                Same as %Y-%m-%d (commonly used in database datestamps)
+     * %s      305815200                 Unix Epoch Time timestamp (same as the time() function)
+     * %x      02/05/09                  Preferred date representation, without the time
+     * 
+     * === Miscellaneous ===            
+     * %n        ---                     A newline character (\n)
+     * %t        ---                     A Tab character (\t)
+     * %%        ---                     A literal percentage character (%)
+     * </pre>
+     */
+ 
+    jsDate.formats.php = {
+        codes: {
+            //
+            // 2-part regex matcher for format codes
+            //
+            // first match must be the character before the code (to account for escaping)
+            // second match must be the format code character(s)
+            //
+            matcher: /()%((%|[a-z]))/i,
+            // day
+            a: 'AbbrDayName',
+            A: 'DayName',
+            d: 'Date.2',
+            e: 'Date',
+            j: 'DayOfYear.3',
+            u: 'DayOfWeek',
+            w: 'Day',
+            // week
+            U: 'FullWeekOfYear.2',
+            V: 'IsoWeek.2',
+            W: 'WeekOfYear.2',
+            // month
+            b: 'AbbrMonthName',
+            B: 'MonthName',
+            m: 'MonthNumber.2',
+            h: 'AbbrMonthName',
+            // year
+            C: 'Century.2',
+            y: 'ShortYear.2',
+            Y: 'FullYear',
+            // time
+            H: 'Hours.2',
+            I: 'Hours12.2',
+            l: 'Hours12',
+            p: 'AMPM',
+            P: 'AmPm',
+            M: 'Minutes.2',
+            S: 'Seconds.2',
+            s: 'Unix',
+            O: 'TimezoneOffset',
+            z: 'GmtOffset',
+            Z: 'TimezoneAbbr'
+        },
+        
+        shortcuts: {
+            D: '%m/%d/%y',
+            F: '%Y-%m-%d',
+            T: '%H:%M:%S',
+            X: '%H:%M:%S',
+            x: '%m/%d/%y',
+            R: '%H:%M',
+            r: '%I:%M:%S %p',
+            t: '\t',
+            n: '\n',
+            '%': '%'
+        }
+    };   
+    //
+    // Conceptually, the logic implemented here is similar to Ken Snyder's Date Instance Methods.
+    // I use his idea of a set of parsers which can be regular expressions or functions,
+    // iterating through those, and then seeing if Date.parse() will create a date.
+    // The parser expressions and functions are a little different and some bugs have been
+    // worked out.  Also, a lot of "pre-parsing" is done to fix implementation
+    // variations of Date.parse() between browsers.
+    //
+    jsDate.createDate = function(date) {
+        // if passing in multiple arguments, try Date constructor
+        if (date == null) {
+            return new Date();
+        }
+        // If the passed value is already a date object, return it
+        if (date instanceof Date) {
+            return date;
+        }
+        // if (typeof date == 'number') return new Date(date * 1000);
+        // If the passed value is an integer, interpret it as a javascript timestamp
+        if (typeof date == 'number') {
+            return new Date(date);
+        }
+        
+        // Before passing strings into Date.parse(), have to normalize them for certain conditions.
+        // If strings are not formatted staccording to the EcmaScript spec, results from Date parse will be implementation dependent.  
+        // 
+        // For example: 
+        //  * FF and Opera assume 2 digit dates are pre y2k, Chome assumes <50 is pre y2k, 50+ is 21st century.  
+        //  * Chrome will correctly parse '1984-1-25' into localtime, FF and Opera will not parse.
+        //  * Both FF, Chrome and Opera will parse '1984/1/25' into localtime.
+        
+        // remove leading and trailing spaces
+        var parsable = String(date).replace(/^\s*(.+)\s*$/g, '$1');
+        
+        // replace dahses (-) with slashes (/) in dates like n[nnn]/n[n]/n[nnn]
+        parsable = parsable.replace(/^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,4})/, "$1/$2/$3");
+        
+        /////////
+        // Need to check for '15-Dec-09' also.
+        // FF will not parse, but Chrome will.
+        // Chrome will set date to 2009 as well.
+        /////////
+        
+        // first check for 'dd-mmm-yyyy' or 'dd/mmm/yyyy' like '15-Dec-2010'
+        parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{4})/i, "$1 $2 $3");
+        
+        // Now check for 'dd-mmm-yy' or 'dd/mmm/yy' and normalize years to default century.
+        var match = parsable.match(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i);
+        if (match && match.length > 3) {
+            var m3 = parseFloat(match[3]);
+            var ny = jsDate.config.defaultCentury + m3;
+            ny = String(ny);
+            
+            // now replace 2 digit year with 4 digit year
+            parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i, match[1] +' '+ match[2] +' '+ ny);
+            
+        }
+        
+        // Check for '1/19/70 8:14PM'
+        // where starts with mm/dd/yy or yy/mm/dd and have something after
+        // Check if 1st postiion is greater than 31, assume it is year.
+        // Assme all 2 digit years are 1900's.
+        // Finally, change them into US style mm/dd/yyyy representations.
+        match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})[^0-9]/);
+        
+        function h1(parsable, match) {
+            var m1 = parseFloat(match[1]);
+            var m2 = parseFloat(match[2]);
+            var m3 = parseFloat(match[3]);
+            var cent = jsDate.config.defaultCentury;
+            var ny, nd, nm, str;
+            
+            if (m1 > 31) { // first number is a year
+                nd = m3;
+                nm = m2;
+                ny = cent + m1;
+            }
+            
+            else { // last number is the year
+                nd = m2;
+                nm = m1;
+                ny = cent + m3;
+            }
+            
+            str = nm+'/'+nd+'/'+ny;
+            
+            // now replace 2 digit year with 4 digit year
+            return  parsable.replace(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})/, str);
+        
+        }
+        
+        if (match && match.length > 3) {
+            parsable = h1(parsable, match);
+        }
+        
+        // Now check for '1/19/70' with nothing after and do as above
+        var match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})$/);
+        
+        if (match && match.length > 3) {
+            parsable = h1(parsable, match);
+        }
+                
+        
+        var i = 0;
+        var length = jsDate.matchers.length;
+        var pattern,
+            ms,
+            current = parsable,
+            obj;
+        while (i < length) {
+            ms = Date.parse(current);
+            if (!isNaN(ms)) {
+                return new Date(ms);
+            }
+            pattern = jsDate.matchers[i];
+            if (typeof pattern == 'function') {
+                obj = pattern.call(jsDate, current);
+                if (obj instanceof Date) {
+                    return obj;
+                }
+            } else {
+                current = parsable.replace(pattern[0], pattern[1]);
+            }
+            i++;
+        }
+        return NaN;
+    };
+    
+
+    /**
+     * @static
+     * Handy static utility function to return the number of days in a given month.
+     * @param {Integer} year Year
+     * @param {Integer} month Month (1-12)
+     * @returns {Integer} Number of days in the month.
+    */
+    //
+    // handy utility method Borrowed right from Ken Snyder's Date Instance Mehtods.
+    // 
+    jsDate.daysInMonth = function(year, month) {
+        if (month == 2) {
+            return new Date(year, 1, 29).getDate() == 29 ? 29 : 28;
+        }
+        return [undefined,31,undefined,31,30,31,30,31,31,30,31,30,31][month];
+    };
+
+
+    //
+    // An Array of regular expressions or functions that will attempt to match the date string.
+    // Functions are called with scope of a jsDate instance.
+    //
+    jsDate.matchers = [
+        // convert dd.mmm.yyyy to mm/dd/yyyy (world date to US date).
+        [/(3[01]|[0-2]\d)\s*\.\s*(1[0-2]|0\d)\s*\.\s*([1-9]\d{3})/, '$2/$1/$3'],
+        // convert yyyy-mm-dd to mm/dd/yyyy (ISO date to US date).
+        [/([1-9]\d{3})\s*-\s*(1[0-2]|0\d)\s*-\s*(3[01]|[0-2]\d)/, '$2/$3/$1'],
+        // Handle 12 hour or 24 hour time with milliseconds am/pm and optional date part.
+        function(str) { 
+            var match = str.match(/^(?:(.+)\s+)?([012]?\d)(?:\s*\:\s*(\d\d))?(?:\s*\:\s*(\d\d(\.\d*)?))?\s*(am|pm)?\s*$/i);
+            //                   opt. date      hour       opt. minute     opt. second       opt. msec   opt. am or pm
+            if (match) {
+                if (match[1]) {
+                    var d = this.createDate(match[1]);
+                    if (isNaN(d)) {
+                        return;
+                    }
+                } else {
+                    var d = new Date();
+                    d.setMilliseconds(0);
+                }
+                var hour = parseFloat(match[2]);
+                if (match[6]) {
+                    hour = match[6].toLowerCase() == 'am' ? (hour == 12 ? 0 : hour) : (hour == 12 ? 12 : hour + 12);
+                }
+                d.setHours(hour, parseInt(match[3] || 0, 10), parseInt(match[4] || 0, 10), ((parseFloat(match[5] || 0)) || 0)*1000);
+                return d;
+            }
+            else {
+                return str;
+            }
+        },
+        // Handle ISO timestamp with time zone.
+        function(str) {
+            var match = str.match(/^(?:(.+))[T|\s+]([012]\d)(?:\:(\d\d))(?:\:(\d\d))(?:\.\d+)([\+\-]\d\d\:\d\d)$/i);
+            if (match) {
+                if (match[1]) {
+                    var d = this.createDate(match[1]);
+                    if (isNaN(d)) {
+                        return;
+                    }
+                } else {
+                    var d = new Date();
+                    d.setMilliseconds(0);
+                }
+                var hour = parseFloat(match[2]);
+                d.setHours(hour, parseInt(match[3], 10), parseInt(match[4], 10), parseFloat(match[5])*1000);
+                return d;
+            }
+            else {
+                    return str;
+            }
+        },
+        // Try to match ambiguous strings like 12/8/22.
+        // Use FF date assumption that 2 digit years are 20th century (i.e. 1900's).
+        // This may be redundant with pre processing of date already performed.
+        function(str) {
+            var match = str.match(/^([0-3]?\d)\s*[-\/.\s]{1}\s*([a-zA-Z]{3,9})\s*[-\/.\s]{1}\s*([0-3]?\d)$/);
+            if (match) {
+                var d = new Date();
+                var cent = jsDate.config.defaultCentury;
+                var m1 = parseFloat(match[1]);
+                var m3 = parseFloat(match[3]);
+                var ny, nd, nm;
+                if (m1 > 31) { // first number is a year
+                    nd = m3;
+                    ny = cent + m1;
+                }
+                
+                else { // last number is the year
+                    nd = m1;
+                    ny = cent + m3;
+                }
+                
+                var nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNamesShort"]);
+                
+                if (nm == -1) {
+                    nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNames"]);
+                }
+            
+                d.setFullYear(ny, nm, nd);
+                d.setHours(0,0,0,0);
+                return d;
+            }
+            
+            else {
+                return str;
+            }
+        }      
+    ];
+
+    //
+    // I think John Reisig published this method on his blog, ejohn.
+    //
+    function inArray( elem, array ) {
+        if ( array.indexOf ) {
+            return array.indexOf( elem );
+        }
+
+        for ( var i = 0, length = array.length; i < length; i++ ) {
+            if ( array[ i ] === elem ) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+    
+    //
+    // Thanks to Kangax, Christian Sciberras and Stack Overflow for this method.
+    //
+    function get_type(thing){
+        if(thing===null) return "[object Null]"; // special case
+        return Object.prototype.toString.call(thing);
+    }
+    
+    $.jsDate = jsDate;
+
+      
+    /**
+     * JavaScript printf/sprintf functions.
+     * 
+     * This code has been adapted from the publicly available sprintf methods
+     * by Ash Searle. His original header follows:
+     *
+     *     This code is unrestricted: you are free to use it however you like.
+     *     
+     *     The functions should work as expected, performing left or right alignment,
+     *     truncating strings, outputting numbers with a required precision etc.
+     *
+     *     For complex cases, these functions follow the Perl implementations of
+     *     (s)printf, allowing arguments to be passed out-of-order, and to set the
+     *     precision or length of the output based on arguments instead of fixed
+     *     numbers.
+     *
+     *     See http://perldoc.perl.org/functions/sprintf.html for more information.
+     *
+     *     Implemented:
+     *     - zero and space-padding
+     *     - right and left-alignment,
+     *     - base X prefix (binary, octal and hex)
+     *     - positive number prefix
+     *     - (minimum) width
+     *     - precision / truncation / maximum width
+     *     - out of order arguments
+     *
+     *     Not implemented (yet):
+     *     - vector flag
+     *     - size (bytes, words, long-words etc.)
+     *     
+     *     Will not implement:
+     *     - %n or %p (no pass-by-reference in JavaScript)
+     *
+     *     @version 2007.04.27
+     *     @author Ash Searle 
+     * 
+     * You can see the original work and comments on his blog:
+     * http://hexmen.com/blog/2007/03/printf-sprintf/
+     * http://hexmen.com/js/sprintf.js
+     */
+     
+     /**
+      * @Modifications 2009.05.26
+      * @author Chris Leonello
+      * 
+      * Added %p %P specifier
+      * Acts like %g or %G but will not add more significant digits to the output than present in the input.
+      * Example:
+      * Format: '%.3p', Input: 0.012, Output: 0.012
+      * Format: '%.3g', Input: 0.012, Output: 0.0120
+      * Format: '%.4p', Input: 12.0, Output: 12.0
+      * Format: '%.4g', Input: 12.0, Output: 12.00
+      * Format: '%.4p', Input: 4.321e-5, Output: 4.321e-5
+      * Format: '%.4g', Input: 4.321e-5, Output: 4.3210e-5
+      * 
+      * Example:
+      * >>> $.jqplot.sprintf('%.2f, %d', 23.3452, 43.23)
+      * "23.35, 43"
+      * >>> $.jqplot.sprintf("no value: %n, decimal with thousands separator: %'d", 23.3452, 433524)
+      * "no value: , decimal with thousands separator: 433,524"
+      */
+    $.jqplot.sprintf = function() {
+        function pad(str, len, chr, leftJustify) {
+            var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
+            return leftJustify ? str + padding : padding + str;
+
+        }
+
+        function thousand_separate(value) {
+            var value_str = new String(value);
+            for (var i=10; i>0; i--) {
+                if (value_str == (value_str = value_str.replace(/^(\d+)(\d{3})/, "$1"+$.jqplot.sprintf.thousandsSeparator+"$2"))) break;
+            }
+            return value_str; 
+        }
+
+        function justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace) {
+            var diff = minWidth - value.length;
+            if (diff > 0) {
+                var spchar = ' ';
+                if (htmlSpace) { spchar = '&nbsp;'; }
+                if (leftJustify || !zeroPad) {
+                    value = pad(value, minWidth, spchar, leftJustify);
+                } else {
+                    value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
+                }
+            }
+            return value;
+        }
+
+        function formatBaseX(value, base, prefix, leftJustify, minWidth, precision, zeroPad, htmlSpace) {
+            // Note: casts negative numbers to positive ones
+            var number = value >>> 0;
+            prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
+            value = prefix + pad(number.toString(base), precision || 0, '0', false);
+            return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
+        }
+
+        function formatString(value, leftJustify, minWidth, precision, zeroPad, htmlSpace) {
+            if (precision != null) {
+                value = value.slice(0, precision);
+            }
+            return justify(value, '', leftJustify, minWidth, zeroPad, htmlSpace);
+        }
+
+        var a = arguments, i = 0, format = a[i++];
+
+        return format.replace($.jqplot.sprintf.regex, function(substring, valueIndex, flags, minWidth, _, precision, type) {
+            if (substring == '%%') { return '%'; }
+
+            // parse flags
+            var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, htmlSpace = false, thousandSeparation = false;
+            for (var j = 0; flags && j < flags.length; j++) switch (flags.charAt(j)) {
+                case ' ': positivePrefix = ' '; break;
+                case '+': positivePrefix = '+'; break;
+                case '-': leftJustify = true; break;
+                case '0': zeroPad = true; break;
+                case '#': prefixBaseX = true; break;
+                case '&': htmlSpace = true; break;
+                case '\'': thousandSeparation = true; break;
+            }
+
+            // parameters may be null, undefined, empty-string or real valued
+            // we want to ignore null, undefined and empty-string values
+
+            if (!minWidth) {
+                minWidth = 0;
+            } 
+            else if (minWidth == '*') {
+                minWidth = +a[i++];
+            } 
+            else if (minWidth.charAt(0) == '*') {
+                minWidth = +a[minWidth.slice(1, -1)];
+            } 
+            else {
+                minWidth = +minWidth;
+            }
+
+            // Note: undocumented perl feature:
+            if (minWidth < 0) {
+                minWidth = -minWidth;
+                leftJustify = true;
+            }
+
+            if (!isFinite(minWidth)) {
+                throw new Error('$.jqplot.sprintf: (minimum-)width must be finite');
+            }
+
+            if (!precision) {
+                precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0);
+            } 
+            else if (precision == '*') {
+                precision = +a[i++];
+            } 
+            else if (precision.charAt(0) == '*') {
+                precision = +a[precision.slice(1, -1)];
+            } 
+            else {
+                precision = +precision;
+            }
+
+            // grab value using valueIndex if required?
+            var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];
+
+            switch (type) {
+            case 's': {
+                if (value == null) {
+                    return '';
+                }
+                return formatString(String(value), leftJustify, minWidth, precision, zeroPad, htmlSpace);
+            }
+            case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad, htmlSpace);
+            case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad,htmlSpace);
+            case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
+            case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
+            case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace).toUpperCase();
+            case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
+            case 'i': {
+              var number = parseInt(+value, 10);
+              if (isNaN(number)) {
+                return '';
+              }
+              var prefix = number < 0 ? '-' : positivePrefix;
+              var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number));
+              value = prefix + pad(number_str, precision, '0', false);
+              //value = prefix + pad(String(Math.abs(number)), precision, '0', false);
+              return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
+                  }
+            case 'd': {
+              var number = Math.round(+value);
+              if (isNaN(number)) {
+                return '';
+              }
+              var prefix = number < 0 ? '-' : positivePrefix;
+              var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number));
+              value = prefix + pad(number_str, precision, '0', false);
+              return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
+                  }
+            case 'e':
+            case 'E':
+            case 'f':
+            case 'F':
+            case 'g':
+            case 'G':
+                      {
+                      var number = +value;
+                      if (isNaN(number)) {
+                          return '';
+                      }
+                      var prefix = number < 0 ? '-' : positivePrefix;
+                      var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
+                      var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
+                      var number_str = Math.abs(number)[method](precision);
+                      
+                      // Apply the decimal mark properly by splitting the number by the
+                      //   decimalMark, applying thousands separator, and then placing it
+                      //   back in.
+                      var parts = number_str.toString().split('.');
+                      parts[0] = thousandSeparation ? thousand_separate(parts[0]) : parts[0];
+                      number_str = parts.join($.jqplot.sprintf.decimalMark);
+                      
+                      value = prefix + number_str;
+                      var justified = justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform]();
+                      
+                      return justified;
+                  }
+            case 'p':
+            case 'P':
+            {
+                // make sure number is a number
+                var number = +value;
+                if (isNaN(number)) {
+                    return '';
+                }
+                var prefix = number < 0 ? '-' : positivePrefix;
+
+                var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/);
+                var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : String(number).length;
+                var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0;
+                
+                if (Math.abs(number) < 1) {
+                    if (sd + zeros  <= precision) {
+                        value = prefix + Math.abs(number).toPrecision(sd);
+                    }
+                    else {
+                        if (sd  <= precision - 1) {
+                            value = prefix + Math.abs(number).toExponential(sd-1);
+                        }
+                        else {
+                            value = prefix + Math.abs(number).toExponential(precision-1);
+                        }
+                    }
+                }
+                else {
+                    var prec = (sd <= precision) ? sd : precision;
+                    value = prefix + Math.abs(number).toPrecision(prec);
+                }
+                var textTransform = ['toString', 'toUpperCase']['pP'.indexOf(type) % 2];
+                return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform]();
+            }
+            case 'n': return '';
+            default: return substring;
+            }
+        });
+    };
+
+    $.jqplot.sprintf.thousandsSeparator = ',';
+    // Specifies the decimal mark for floating point values. By default a period '.'
+    // is used. If you change this value to for example a comma be sure to also
+    // change the thousands separator or else this won't work since a simple String
+    // replace is used (replacing all periods with the mark specified here).
+    $.jqplot.sprintf.decimalMark = '.';
+    
+    $.jqplot.sprintf.regex = /%%|%(\d+\$)?([-+#0&\' ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([nAscboxXuidfegpEGP])/g;
+
+    $.jqplot.getSignificantFigures = function(number) {
+        var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/);
+        // total significant digits
+        var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length;
+        var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0;
+        // exponent
+        var expn = parseInt(parts[1], 10);
+        // digits to the left of the decimal place
+        var dleft = (expn + 1 > 0) ? expn + 1 : 0;
+        // digits to the right of the decimal place
+        var dright = (sd <= dleft) ? 0 : sd - expn - 1;
+        return {significantDigits: sd, digitsLeft: dleft, digitsRight: dright, zeros: zeros, exponent: expn} ;
+    };
+
+    $.jqplot.getPrecision = function(number) {
+        return $.jqplot.getSignificantFigures(number).digitsRight;
+    };
+
+  
+
+
+    var backCompat = $.uiBackCompat !== false;
+
+    $.jqplot.effects = {
+        effect: {}
+    };
+
+    // prefix used for storing data on .data()
+    var dataSpace = "jqplot.storage.";
+
+    /******************************************************************************/
+    /*********************************** EFFECTS **********************************/
+    /******************************************************************************/
+
+    $.extend( $.jqplot.effects, {
+        version: "1.9pre",
+
+        // Saves a set of properties in a data storage
+        save: function( element, set ) {
+            for( var i=0; i < set.length; i++ ) {
+                if ( set[ i ] !== null ) {
+                    element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
+                }
+            }
+        },
+
+        // Restores a set of previously saved properties from a data storage
+        restore: function( element, set ) {
+            for( var i=0; i < set.length; i++ ) {
+                if ( set[ i ] !== null ) {
+                    element.css( set[ i ], element.data( dataSpace + set[ i ] ) );
+                }
+            }
+        },
+
+        setMode: function( el, mode ) {
+            if (mode === "toggle") {
+                mode = el.is( ":hidden" ) ? "show" : "hide";
+            }
+            return mode;
+        },
+
+        // Wraps the element around a wrapper that copies position properties
+        createWrapper: function( element ) {
+
+            // if the element is already wrapped, return it
+            if ( element.parent().is( ".ui-effects-wrapper" )) {
+                return element.parent();
+            }
+
+            // wrap the element
+            var props = {
+                    width: element.outerWidth(true),
+                    height: element.outerHeight(true),
+                    "float": element.css( "float" )
+                },
+                wrapper = $( "<div></div>" )
+                    .addClass( "ui-effects-wrapper" )
+                    .css({
+                        fontSize: "100%",
+                        background: "transparent",
+                        border: "none",
+                        margin: 0,
+                        padding: 0
+                    }),
+                // Store the size in case width/height are defined in % - Fixes #5245
+                size = {
+                    width: element.width(),
+                    height: element.height()
+                },
+                active = document.activeElement;
+
+            element.wrap( wrapper );
+
+            // Fixes #7595 - Elements lose focus when wrapped.
+            if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+                $( active ).focus();
+            }
+
+            wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element
+
+            // transfer positioning properties to the wrapper
+            if ( element.css( "position" ) === "static" ) {
+                wrapper.css({ position: "relative" });
+                element.css({ position: "relative" });
+            } else {
+                $.extend( props, {
+                    position: element.css( "position" ),
+                    zIndex: element.css( "z-index" )
+                });
+                $.each([ "top", "left", "bottom", "right" ], function(i, pos) {
+                    props[ pos ] = element.css( pos );
+                    if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
+                        props[ pos ] = "auto";
+                    }
+                });
+                element.css({
+                    position: "relative",
+                    top: 0,
+                    left: 0,
+                    right: "auto",
+                    bottom: "auto"
+                });
+            }
+            element.css(size);
+
+            return wrapper.css( props ).show();
+        },
+
+        removeWrapper: function( element ) {
+            var active = document.activeElement;
+
+            if ( element.parent().is( ".ui-effects-wrapper" ) ) {
+                element.parent().replaceWith( element );
+
+                // Fixes #7595 - Elements lose focus when wrapped.
+                if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+                    $( active ).focus();
+                }
+            }
+
+
+            return element;
+        }
+    });
+
+    // return an effect options object for the given parameters:
+    function _normalizeArguments( effect, options, speed, callback ) {
+
+        // short path for passing an effect options object:
+        if ( $.isPlainObject( effect ) ) {
+            return effect;
+        }
+
+        // convert to an object
+        effect = { effect: effect };
+
+        // catch (effect)
+        if ( options === undefined ) {
+            options = {};
+        }
+
+        // catch (effect, callback)
+        if ( $.isFunction( options ) ) {
+            callback = options;
+            speed = null;
+            options = {};
+        }
+
+        // catch (effect, speed, ?)
+        if ( $.type( options ) === "number" || $.fx.speeds[ options ]) {
+            callback = speed;
+            speed = options;
+            options = {};
+        }
+
+        // catch (effect, options, callback)
+        if ( $.isFunction( speed ) ) {
+            callback = speed;
+            speed = null;
+        }
+
+        // add options to effect
+        if ( options ) {
+            $.extend( effect, options );
+        }
+
+        speed = speed || options.duration;
+        effect.duration = $.fx.off ? 0 : typeof speed === "number"
+            ? speed : speed in $.fx.speeds ? $.fx.speeds[ speed ] : $.fx.speeds._default;
+
+        effect.complete = callback || options.complete;
+
+        return effect;
+    }
+
+    function standardSpeed( speed ) {
+        // valid standard speeds
+        if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) {
+            return true;
+        }
+
+        // invalid strings - treat as "normal" speed
+        if ( typeof speed === "string" && !$.jqplot.effects.effect[ speed ] ) {
+            // TODO: remove in 2.0 (#7115)
+            if ( backCompat && $.jqplot.effects[ speed ] ) {
+                return false;
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    $.fn.extend({
+        jqplotEffect: function( effect, options, speed, callback ) {
+            var args = _normalizeArguments.apply( this, arguments ),
+                mode = args.mode,
+                queue = args.queue,
+                effectMethod = $.jqplot.effects.effect[ args.effect ],
+
+                // DEPRECATED: remove in 2.0 (#7115)
+                oldEffectMethod = !effectMethod && backCompat && $.jqplot.effects[ args.effect ];
+
+            if ( $.fx.off || !( effectMethod || oldEffectMethod ) ) {
+                // delegate to the original method (e.g., .show()) if possible
+                if ( mode ) {
+                    return this[ mode ]( args.duration, args.complete );
+                } else {
+                    return this.each( function() {
+                        if ( args.complete ) {
+                            args.complete.call( this );
+                        }
+                    });
+                }
+            }
+
+            function run( next ) {
+                var elem = $( this ),
+                    complete = args.complete,
+                    mode = args.mode;
+
+                function done() {
+                    if ( $.isFunction( complete ) ) {
+                        complete.call( elem[0] );
+                    }
+                    if ( $.isFunction( next ) ) {
+                        next();
+                    }
+                }
+
+                // if the element is hiddden and mode is hide,
+                // or element is visible and mode is show
+                if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
+                    done();
+                } else {
+                    effectMethod.call( elem[0], args, done );
+                }
+            }
+
+            // TODO: remove this check in 2.0, effectMethod will always be true
+            if ( effectMethod ) {
+                return queue === false ? this.each( run ) : this.queue( queue || "fx", run );
+            } else {
+                // DEPRECATED: remove in 2.0 (#7115)
+                return oldEffectMethod.call(this, {
+                    options: args,
+                    duration: args.duration,
+                    callback: args.complete,
+                    mode: args.mode
+                });
+            }
+        }
+    });
+
+
+
+
+    var rvertical = /up|down|vertical/,
+        rpositivemotion = /up|left|vertical|horizontal/;
+
+    $.jqplot.effects.effect.blind = function( o, done ) {
+        // Create element
+        var el = $( this ),
+            props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+            mode = $.jqplot.effects.setMode( el, o.mode || "hide" ),
+            direction = o.direction || "up",
+            vertical = rvertical.test( direction ),
+            ref = vertical ? "height" : "width",
+            ref2 = vertical ? "top" : "left",
+            motion = rpositivemotion.test( direction ),
+            animation = {},
+            show = mode === "show",
+            wrapper, distance, top;
+
+        // // if already wrapped, the wrapper's properties are my property. #6245
+        if ( el.parent().is( ".ui-effects-wrapper" ) ) {
+            $.jqplot.effects.save( el.parent(), props );
+        } else {
+            $.jqplot.effects.save( el, props );
+        }
+        el.show();
+        top = parseInt(el.css('top'), 10);
+        wrapper = $.jqplot.effects.createWrapper( el ).css({
+            overflow: "hidden"
+        });
+
+        distance = vertical ? wrapper[ ref ]() + top : wrapper[ ref ]();
+
+        animation[ ref ] = show ? String(distance) : '0';
+        if ( !motion ) {
+            el
+                .css( vertical ? "bottom" : "right", 0 )
+                .css( vertical ? "top" : "left", "" )
+                .css({ position: "absolute" });
+            animation[ ref2 ] = show ? '0' : String(distance);
+        }
+
+        // // start at 0 if we are showing
+        if ( show ) {
+            wrapper.css( ref, 0 );
+            if ( ! motion ) {
+                wrapper.css( ref2, distance );
+            }
+        }
+
+        // // Animate
+        wrapper.animate( animation, {
+            duration: o.duration,
+            easing: o.easing,
+            queue: false,
+            complete: function() {
+                if ( mode === "hide" ) {
+                    el.hide();
+                }
+                $.jqplot.effects.restore( el, props );
+                $.jqplot.effects.removeWrapper( el );
+                done();
+            }
+        });
+
+    };
+
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.BezierCurveRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.BezierCurveRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.BezierCurveRenderer.js	(revision 2119)
@@ -0,0 +1,314 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    // Class: $.jqplot.BezierCurveRenderer.js
+    // Renderer which draws lines as stacked bezier curves.
+    // Data for the line will not be specified as an array of
+    // [x, y] data point values, but as a an array of [start piont, bezier curve]
+    // So, the line is specified as: [[xstart, ystart], [cp1x, cp1y, cp2x, cp2y, xend, yend]].
+    $.jqplot.BezierCurveRenderer = function(){
+        $.jqplot.LineRenderer.call(this);
+    };
+    
+    $.jqplot.BezierCurveRenderer.prototype = new $.jqplot.LineRenderer();
+    $.jqplot.BezierCurveRenderer.prototype.constructor = $.jqplot.BezierCurveRenderer;
+
+    
+    // Method: setGridData
+    // converts the user data values to grid coordinates and stores them
+    // in the gridData array.
+    // Called with scope of a series.
+    $.jqplot.BezierCurveRenderer.prototype.setGridData = function(plot) {
+        // recalculate the grid data
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        // this._plotData should be same as this.data
+        var data = this.data;
+        this.gridData = [];
+        this._prevGridData = [];
+        // if seriesIndex = 0, fill to x axis.
+        // if seriesIndex > 0, fill to previous series data.
+        var idx = this.index;
+        if (data.length == 2) {
+            if (idx == 0) {
+                this.gridData = [
+                    [xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, data[0][1])], 
+                    [xp.call(this._xaxis, data[1][0]), yp.call(this._yaxis, data[1][1]), 
+                        xp.call(this._xaxis, data[1][2]), yp.call(this._yaxis, data[1][3]),  
+                        xp.call(this._xaxis, data[1][4]), yp.call(this._yaxis, data[1][5])],
+                    [xp.call(this._xaxis, data[1][4]), yp.call(this._yaxis, this._yaxis.min)],
+                    [xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, this._yaxis.min)]
+                ];
+            }
+            else {
+                var psd = plot.series[idx-1].data;
+                this.gridData = [
+                    [xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, data[0][1])], 
+                    [xp.call(this._xaxis, data[1][0]), yp.call(this._yaxis, data[1][1]), 
+                        xp.call(this._xaxis, data[1][2]), yp.call(this._yaxis, data[1][3]),  
+                        xp.call(this._xaxis, data[1][4]), yp.call(this._yaxis, data[1][5])],
+                    [xp.call(this._xaxis, psd[1][4]), yp.call(this._yaxis, psd[1][5])],
+                    [xp.call(this._xaxis, psd[1][2]), yp.call(this._yaxis, psd[1][3]), 
+                        xp.call(this._xaxis, psd[1][0]), yp.call(this._yaxis, psd[1][1]),  
+                        xp.call(this._xaxis, psd[0][0]), yp.call(this._yaxis, psd[0][1])]
+                ];
+            }
+        }
+        else {
+            if (idx == 0) {
+                this.gridData = [
+                    [xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, data[0][1])], 
+                    [xp.call(this._xaxis, data[1][0]), yp.call(this._yaxis, data[1][1]), 
+                        xp.call(this._xaxis, data[2][0]), yp.call(this._yaxis, data[2][1]),  
+                        xp.call(this._xaxis, data[3][0]), yp.call(this._yaxis, data[3][1])],
+                    [xp.call(this._xaxis, data[3][1]), yp.call(this._yaxis, this._yaxis.min)],
+                    [xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, this._yaxis.min)]
+                ];
+            }
+            else {
+                var psd = plot.series[idx-1].data;
+                this.gridData = [
+                    [xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, data[0][1])], 
+                    [xp.call(this._xaxis, data[1][0]), yp.call(this._yaxis, data[1][1]), 
+                        xp.call(this._xaxis, data[2][0]), yp.call(this._yaxis, data[2][1]),  
+                        xp.call(this._xaxis, data[3][0]), yp.call(this._yaxis, data[3][1])],
+                    [xp.call(this._xaxis, psd[3][0]), yp.call(this._yaxis, psd[3][1])],
+                    [xp.call(this._xaxis, psd[2][0]), yp.call(this._yaxis, psd[2][1]), 
+                        xp.call(this._xaxis, psd[1][0]), yp.call(this._yaxis, psd[1][1]),  
+                        xp.call(this._xaxis, psd[0][0]), yp.call(this._yaxis, psd[0][1])]
+                ];
+            }
+        }
+    };
+    
+    // Method: makeGridData
+    // converts any arbitrary data values to grid coordinates and
+    // returns them.  This method exists so that plugins can use a series'
+    // linerenderer to generate grid data points without overwriting the
+    // grid data associated with that series.
+    // Called with scope of a series.
+    $.jqplot.BezierCurveRenderer.prototype.makeGridData = function(data, plot) {
+        // recalculate the grid data
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        var gd = [];
+        var pgd = [];
+        // if seriesIndex = 0, fill to x axis.
+        // if seriesIndex > 0, fill to previous series data.
+        var idx = this.index;
+        if (data.length == 2) {
+            if (idx == 0) {
+                gd = [
+                    [xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, data[0][1])], 
+                    [xp.call(this._xaxis, data[1][0]), yp.call(this._yaxis, data[1][1]), 
+                        xp.call(this._xaxis, data[1][2]), yp.call(this._yaxis, data[1][3]),  
+                        xp.call(this._xaxis, data[1][4]), yp.call(this._yaxis, data[1][5])],
+                    [xp.call(this._xaxis, data[1][4]), yp.call(this._yaxis, this._yaxis.min)],
+                    [xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, this._yaxis.min)]
+                ];
+            }
+            else {
+                var psd = plot.series[idx-1].data;
+                gd = [
+                    [xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, data[0][1])], 
+                    [xp.call(this._xaxis, data[1][0]), yp.call(this._yaxis, data[1][1]), 
+                        xp.call(this._xaxis, data[1][2]), yp.call(this._yaxis, data[1][3]),  
+                        xp.call(this._xaxis, data[1][4]), yp.call(this._yaxis, data[1][5])],
+                    [xp.call(this._xaxis, psd[1][4]), yp.call(this._yaxis, psd[1][5])],
+                    [xp.call(this._xaxis, psd[1][2]), yp.call(this._yaxis, psd[1][3]), 
+                        xp.call(this._xaxis, psd[1][0]), yp.call(this._yaxis, psd[1][1]),  
+                        xp.call(this._xaxis, psd[0][0]), yp.call(this._yaxis, psd[0][1])]
+                ];
+            }
+        }
+        else {
+            if (idx == 0) {
+                gd = [
+                    [xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, data[0][1])], 
+                    [xp.call(this._xaxis, data[1][0]), yp.call(this._yaxis, data[1][1]), 
+                        xp.call(this._xaxis, data[2][0]), yp.call(this._yaxis, data[2][1]),  
+                        xp.call(this._xaxis, data[3][0]), yp.call(this._yaxis, data[3][1])],
+                    [xp.call(this._xaxis, data[3][1]), yp.call(this._yaxis, this._yaxis.min)],
+                    [xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, this._yaxis.min)]
+                ];
+            }
+            else {
+                var psd = plot.series[idx-1].data;
+                gd = [
+                    [xp.call(this._xaxis, data[0][0]), yp.call(this._yaxis, data[0][1])], 
+                    [xp.call(this._xaxis, data[1][0]), yp.call(this._yaxis, data[1][1]), 
+                        xp.call(this._xaxis, data[2][0]), yp.call(this._yaxis, data[2][1]),  
+                        xp.call(this._xaxis, data[3][0]), yp.call(this._yaxis, data[3][1])],
+                    [xp.call(this._xaxis, psd[3][0]), yp.call(this._yaxis, psd[3][1])],
+                    [xp.call(this._xaxis, psd[2][0]), yp.call(this._yaxis, psd[2][1]), 
+                        xp.call(this._xaxis, psd[1][0]), yp.call(this._yaxis, psd[1][1]),  
+                        xp.call(this._xaxis, psd[0][0]), yp.call(this._yaxis, psd[0][1])]
+                ];
+            }
+        }
+        return gd;
+    };
+    
+
+    // called within scope of series.
+    $.jqplot.BezierCurveRenderer.prototype.draw = function(ctx, gd, options) {
+        var i;
+        ctx.save();
+        if (gd.length) {
+            if (this.showLine) {
+                ctx.save();
+                var opts = (options != null) ? options : {};
+                ctx.fillStyle = opts.fillStyle || this.color;
+                ctx.beginPath();
+                ctx.moveTo(gd[0][0], gd[0][1]);
+                ctx.bezierCurveTo(gd[1][0], gd[1][1], gd[1][2], gd[1][3], gd[1][4], gd[1][5]);
+                ctx.lineTo(gd[2][0], gd[2][1]);
+                if (gd[3].length == 2) {
+                    ctx.lineTo(gd[3][0], gd[3][1]);
+                }
+                else {
+                    ctx.bezierCurveTo(gd[3][0], gd[3][1], gd[3][2], gd[3][3], gd[3][4], gd[3][5]);
+                }
+                ctx.closePath();
+                ctx.fill();
+                ctx.restore();
+            }
+        }
+        
+        ctx.restore();
+    };  
+    
+    $.jqplot.BezierCurveRenderer.prototype.drawShadow = function(ctx, gd, options) {
+        // This is a no-op, shadows drawn with lines.
+    };
+    
+    $.jqplot.BezierAxisRenderer = function() {
+        $.jqplot.LinearAxisRenderer.call(this);
+    };
+    
+    $.jqplot.BezierAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+    $.jqplot.BezierAxisRenderer.prototype.constructor = $.jqplot.BezierAxisRenderer;
+        
+    
+    // Axes on a plot with Bezier Curves
+    $.jqplot.BezierAxisRenderer.prototype.init = function(options){
+        $.extend(true, this, options);
+        var db = this._dataBounds;
+        // Go through all the series attached to this axis and find
+        // the min/max bounds for this axis.
+        for (var i=0; i<this._series.length; i++) {
+            var s = this._series[i];
+            var d = s.data;  
+            if (d.length == 4) {
+                for (var j=0; j<d.length; j++) { 
+                    if (this.name == 'xaxis' || this.name == 'x2axis') {
+                        if (d[j][0] < db.min || db.min == null) {
+                            db.min = d[j][0];
+                        }
+                        if (d[j][0] > db.max || db.max == null) {
+                            db.max = d[j][0];
+                        }
+                    }              
+                    else {
+                        if (d[j][1] < db.min || db.min == null) {
+                            db.min = d[j][1];
+                        }
+                        if (d[j][1] > db.max || db.max == null) {
+                            db.max = d[j][1];
+                        }
+                    }              
+                }
+            }          
+            else {    
+                if (this.name == 'xaxis' || this.name == 'x2axis') {
+                    if (d[0][0] < db.min || db.min == null) {
+                        db.min = d[0][0];
+                    }
+                    if (d[0][0] > db.max || db.max == null) {
+                        db.max = d[0][0];
+                    }
+                    for (var j=0; j<5; j+=2) {
+                        if (d[1][j] < db.min || db.min == null) {
+                            db.min = d[1][j];
+                        }
+                        if (d[1][j] > db.max || db.max == null) {
+                            db.max = d[1][j];
+                        }
+                    }
+                }              
+                else {
+                    if (d[0][1] < db.min || db.min == null) {
+                        db.min = d[0][1];
+                    }
+                    if (d[0][1] > db.max || db.max == null) {
+                        db.max = d[0][1];
+                    }
+                    for (var j=1; j<6; j+=2) {
+                        if (d[1][j] < db.min || db.min == null) {
+                            db.min = d[1][j];
+                        }
+                        if (d[1][j] > db.max || db.max == null) {
+                            db.max = d[1][j];
+                        }
+                    }
+                }           
+            }
+        }
+    };
+    
+    // setup default renderers for axes and legend so user doesn't have to
+    // called with scope of plot
+    function preInit(target, data, options) {
+        options = options || {};
+        options.axesDefaults = $.extend(true, {pad:0}, options.axesDefaults);
+        options.seriesDefaults = options.seriesDefaults || {};
+        options.legend = $.extend(true, {placement:'outside'}, options.legend);
+        // only set these if there is a pie series
+        var setopts = false;
+        if (options.seriesDefaults.renderer == $.jqplot.BezierCurveRenderer) {
+            setopts = true;
+        }
+        else if (options.series) {
+            for (var i=0; i < options.series.length; i++) {
+                if (options.series[i].renderer == $.jqplot.BezierCurveRenderer) {
+                    setopts = true;
+                }
+            }
+        }
+        
+        if (setopts) {
+            options.axesDefaults.renderer = $.jqplot.BezierAxisRenderer;
+        }
+    }
+    
+    $.jqplot.preInitHooks.push(preInit);
+    
+})(jQuery);    
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.barRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.barRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.barRenderer.js	(revision 2119)
@@ -0,0 +1,801 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    
+    // Class: $.jqplot.BarRenderer
+    // A plugin renderer for jqPlot to draw a bar plot.
+    // Draws series as a line.
+    
+    $.jqplot.BarRenderer = function(){
+        $.jqplot.LineRenderer.call(this);
+    };
+    
+    $.jqplot.BarRenderer.prototype = new $.jqplot.LineRenderer();
+    $.jqplot.BarRenderer.prototype.constructor = $.jqplot.BarRenderer;
+    
+    // called with scope of series.
+    $.jqplot.BarRenderer.prototype.init = function(options, plot) {
+        // Group: Properties
+        //
+        // prop: barPadding
+        // Number of pixels between adjacent bars at the same axis value.
+        this.barPadding = 8;
+        // prop: barMargin
+        // Number of pixels between groups of bars at adjacent axis values.
+        this.barMargin = 10;
+        // prop: barDirection
+        // 'vertical' = up and down bars, 'horizontal' = side to side bars
+        this.barDirection = 'vertical';
+        // prop: barWidth
+        // Width of the bar in pixels (auto by devaul).  null = calculated automatically.
+        this.barWidth = null;
+        // prop: shadowOffset
+        // offset of the shadow from the slice and offset of 
+        // each succesive stroke of the shadow from the last.
+        this.shadowOffset = 2;
+        // prop: shadowDepth
+        // number of strokes to apply to the shadow, 
+        // each stroke offset shadowOffset from the last.
+        this.shadowDepth = 5;
+        // prop: shadowAlpha
+        // transparency of the shadow (0 = transparent, 1 = opaque)
+        this.shadowAlpha = 0.08;
+        // prop: waterfall
+        // true to enable waterfall plot.
+        this.waterfall = false;
+        // prop: groups
+        // group bars into this many groups
+        this.groups = 1;
+        // prop: varyBarColor
+        // true to color each bar of a series separately rather than
+        // have every bar of a given series the same color.
+        // If used for non-stacked multiple series bar plots, user should
+        // specify a separate 'seriesColors' array for each series.
+        // Otherwise, each series will set their bars to the same color array.
+        // This option has no Effect for stacked bar charts and is disabled.
+        this.varyBarColor = false;
+        // prop: highlightMouseOver
+        // True to highlight slice when moused over.
+        // This must be false to enable highlightMouseDown to highlight when clicking on a slice.
+        this.highlightMouseOver = true;
+        // prop: highlightMouseDown
+        // True to highlight when a mouse button is pressed over a slice.
+        // This will be disabled if highlightMouseOver is true.
+        this.highlightMouseDown = false;
+        // prop: highlightColors
+        // an array of colors to use when highlighting a bar.
+        this.highlightColors = [];
+        // prop: transposedData
+        // NOT IMPLEMENTED YET.  True if this is a horizontal bar plot and 
+        // x and y values are "transposed".  Tranposed, or "swapped", data is 
+        // required prior to rev. 894 builds of jqPlot with horizontal bars. 
+        // Allows backward compatability of bar renderer horizontal bars with 
+        // old style data sets.
+        this.transposedData = true;
+        this.renderer.animation = {
+            show: false,
+            direction: 'down',
+            speed: 3000,
+            _supported: true
+        };
+        this._type = 'bar';
+        
+        // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
+        if (options.highlightMouseDown && options.highlightMouseOver == null) {
+            options.highlightMouseOver = false;
+        }
+        
+        //////
+        // This is probably wrong here.
+        // After going back and forth on whether renderer should be the thing
+        // or extend the thing, it seems that it it best if it is a property
+        // on the thing.  This should be something that is commonized 
+        // among series renderers in the future.
+        //////
+        $.extend(true, this, options);
+
+        // really should probably do this
+        $.extend(true, this.renderer, options);
+        // fill is still needed to properly draw the legend.
+        // bars have to be filled.
+        this.fill = true;
+
+        // if horizontal bar and animating, reset the default direction
+        if (this.barDirection === 'horizontal' && this.rendererOptions.animation && this.rendererOptions.animation.direction == null) {
+            this.renderer.animation.direction = 'left';
+        }
+        
+        if (this.waterfall) {
+            this.fillToZero = false;
+            this.disableStack = true;
+        }
+        
+        if (this.barDirection == 'vertical' ) {
+            this._primaryAxis = '_xaxis';
+            this._stackAxis = 'y';
+            this.fillAxis = 'y';
+        }
+        else {
+            this._primaryAxis = '_yaxis';
+            this._stackAxis = 'x';
+            this.fillAxis = 'x';
+        }
+        // index of the currenty highlighted point, if any
+        this._highlightedPoint = null;
+        // total number of values for all bar series, total number of bar series, and position of this series
+        this._plotSeriesInfo = null;
+        // Array of actual data colors used for each data point.
+        this._dataColors = [];
+        this._barPoints = [];
+        
+        // set the shape renderer options
+        var opts = {lineJoin:'miter', lineCap:'round', fill:true, isarc:false, strokeStyle:this.color, fillStyle:this.color, closePath:this.fill};
+        this.renderer.shapeRenderer.init(opts);
+        // set the shadow renderer options
+        var sopts = {lineJoin:'miter', lineCap:'round', fill:true, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, closePath:this.fill};
+        this.renderer.shadowRenderer.init(sopts);
+        
+        plot.postInitHooks.addOnce(postInit);
+        plot.postDrawHooks.addOnce(postPlotDraw);
+        plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
+        plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
+        plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
+        plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
+        plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick); 
+    };
+    
+    // called with scope of series
+    function barPreInit(target, data, seriesDefaults, options) {
+        if (this.rendererOptions.barDirection == 'horizontal') {
+            this._stackAxis = 'x';
+            this._primaryAxis = '_yaxis';
+        }
+        if (this.rendererOptions.waterfall == true) {
+            this._data = $.extend(true, [], this.data);
+            var sum = 0;
+            var pos = (!this.rendererOptions.barDirection || this.rendererOptions.barDirection === 'vertical' || this.transposedData === false) ? 1 : 0;
+            for(var i=0; i<this.data.length; i++) {
+                sum += this.data[i][pos];
+                if (i>0) {
+                    this.data[i][pos] += this.data[i-1][pos];
+                }
+            }
+            this.data[this.data.length] = (pos == 1) ? [this.data.length+1, sum] : [sum, this.data.length+1];
+            this._data[this._data.length] = (pos == 1) ? [this._data.length+1, sum] : [sum, this._data.length+1];
+        }
+        if (this.rendererOptions.groups > 1) {
+            this.breakOnNull = true;
+            var l = this.data.length;
+            var skip = parseInt(l/this.rendererOptions.groups, 10);
+            var count = 0;
+            for (var i=skip; i<l; i+=skip) {
+                this.data.splice(i+count, 0, [null, null]);
+                this._plotData.splice(i+count, 0, [null, null]);
+                this._stackData.splice(i+count, 0, [null, null]);
+                count++;
+            }
+            for (i=0; i<this.data.length; i++) {
+                if (this._primaryAxis == '_xaxis') {
+                    this.data[i][0] = i+1;
+                    this._plotData[i][0] = i+1;
+                    this._stackData[i][0] = i+1;
+                }
+                else {
+                    this.data[i][1] = i+1;
+                    this._plotData[i][1] = i+1;
+                    this._stackData[i][1] = i+1;
+                }
+            }
+        }
+    }
+    
+    $.jqplot.preSeriesInitHooks.push(barPreInit);
+    
+    // needs to be called with scope of series, not renderer.
+    $.jqplot.BarRenderer.prototype.calcSeriesNumbers = function() {
+        var nvals = 0;
+        var nseries = 0;
+        var paxis = this[this._primaryAxis];
+        var s, series, pos;
+        // loop through all series on this axis
+        for (var i=0; i < paxis._series.length; i++) {
+            series = paxis._series[i];
+            if (series === this) {
+                pos = i;
+            }
+            // is the series rendered as a bar?
+            if (series.renderer.constructor == $.jqplot.BarRenderer) {
+                // gridData may not be computed yet, use data length insted
+                nvals += series.data.length;
+                nseries += 1;
+            }
+        }
+        // return total number of values for all bar series, total number of bar series, and position of this series
+        return [nvals, nseries, pos];
+    };
+
+    $.jqplot.BarRenderer.prototype.setBarWidth = function() {
+        // need to know how many data values we have on the approprate axis and figure it out.
+        var i;
+        var nvals = 0;
+        var nseries = 0;
+        var paxis = this[this._primaryAxis];
+        var s, series, pos;
+        var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
+        nvals = temp[0];
+        nseries = temp[1];
+        var nticks = paxis.numberTicks;
+        var nbins = (nticks-1)/2;
+        // so, now we have total number of axis values.
+        if (paxis.name == 'xaxis' || paxis.name == 'x2axis') {
+            if (this._stack) {
+                this.barWidth = (paxis._offsets.max - paxis._offsets.min) / nvals * nseries - this.barMargin;
+            }
+            else {
+                this.barWidth = ((paxis._offsets.max - paxis._offsets.min)/nbins  - this.barPadding * (nseries-1) - this.barMargin*2)/nseries;
+                // this.barWidth = (paxis._offsets.max - paxis._offsets.min) / nvals - this.barPadding - this.barMargin/nseries;
+            }
+        }
+        else {
+            if (this._stack) {
+                this.barWidth = (paxis._offsets.min - paxis._offsets.max) / nvals * nseries - this.barMargin;
+            }
+            else {
+                this.barWidth = ((paxis._offsets.min - paxis._offsets.max)/nbins  - this.barPadding * (nseries-1) - this.barMargin*2)/nseries;
+                // this.barWidth = (paxis._offsets.min - paxis._offsets.max) / nvals - this.barPadding - this.barMargin/nseries;
+            }
+        }
+        return [nvals, nseries];
+    };
+
+    function computeHighlightColors (colors) {
+        var ret = [];
+        for (var i=0; i<colors.length; i++){
+            var rgba = $.jqplot.getColorComponents(colors[i]);
+            var newrgb = [rgba[0], rgba[1], rgba[2]];
+            var sum = newrgb[0] + newrgb[1] + newrgb[2];
+            for (var j=0; j<3; j++) {
+                // when darkening, lowest color component can be is 60.
+                newrgb[j] = (sum > 570) ?  newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
+                newrgb[j] = parseInt(newrgb[j], 10);
+            }
+            ret.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
+        }
+        return ret;
+    }
+
+    function getStart(sidx, didx, comp, plot, axis) {
+        // check if sign change
+        var seriesIndex = sidx,
+            prevSeriesIndex = sidx - 1,
+            start,
+            prevVal,
+            aidx = (axis === 'x') ? 0 : 1;
+
+        // is this not the first series?
+        if (seriesIndex > 0) {
+            prevVal = plot.series[prevSeriesIndex]._plotData[didx][aidx];
+
+            // is there a sign change
+            if ((comp * prevVal) < 0) {
+                start = getStart(prevSeriesIndex, didx, comp, plot, axis);
+            }
+
+            // no sign change.
+            else {
+                start = plot.series[prevSeriesIndex].gridData[didx][aidx];
+            }
+
+        }
+
+        // if first series, return value at 0
+        else {
+
+            start = (aidx === 0) ? plot.series[seriesIndex]._xaxis.series_u2p(0) : plot.series[seriesIndex]._yaxis.series_u2p(0);
+        }
+
+        return start;
+    }
+
+    
+    $.jqplot.BarRenderer.prototype.draw = function(ctx, gridData, options, plot) {
+        var i;
+        // Ughhh, have to make a copy of options b/c it may be modified later.
+        var opts = $.extend({}, options);
+        var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+        var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
+        var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+        var xaxis = this.xaxis;
+        var yaxis = this.yaxis;
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        var pointx, pointy;
+        // clear out data colors.
+        this._dataColors = [];
+        this._barPoints = [];
+        
+        if (this.barWidth == null) {
+            this.renderer.setBarWidth.call(this);
+        }
+        
+        var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
+        var nvals = temp[0];
+        var nseries = temp[1];
+        var pos = temp[2];
+        var points = [];
+        
+        if (this._stack) {
+            this._barNudge = 0;
+        }
+        else {
+            this._barNudge = (-Math.abs(nseries/2 - 0.5) + pos) * (this.barWidth + this.barPadding);
+        }
+        if (showLine) {
+            var negativeColors = new $.jqplot.ColorGenerator(this.negativeSeriesColors);
+            var positiveColors = new $.jqplot.ColorGenerator(this.seriesColors);
+            var negativeColor = negativeColors.get(this.index);
+            if (! this.useNegativeColors) {
+                negativeColor = opts.fillStyle;
+            }
+            var positiveColor = opts.fillStyle;
+            var base;
+            var xstart; 
+            var ystart;
+            
+            if (this.barDirection == 'vertical') {
+                for (var i=0; i<gridData.length; i++) {
+                    if (!this._stack && this.data[i][1] == null) {
+                        continue;
+                    }
+                    points = [];
+                    base = gridData[i][0] + this._barNudge;
+                    
+                    // stacked
+                    if (this._stack && this._prevGridData.length) {
+                        ystart = getStart(this.index, i, this._plotData[i][1], plot, 'y');
+                    }
+
+                    // not stacked
+                    else {
+                        if (this.fillToZero) {
+                            ystart = this._yaxis.series_u2p(0);
+                        }
+                        else if (this.waterfall && i > 0 && i < this.gridData.length-1) {
+                            ystart = this.gridData[i-1][1];
+                        }
+                        else if (this.waterfall && i == 0 && i < this.gridData.length-1) {
+                            if (this._yaxis.min <= 0 && this._yaxis.max >= 0) {
+                                ystart = this._yaxis.series_u2p(0);
+                            }
+                            else if (this._yaxis.min > 0) {
+                                ystart = ctx.canvas.height;
+                            }
+                            else {
+                                ystart = 0;
+                            }
+                        }
+                        else if (this.waterfall && i == this.gridData.length - 1) {
+                            if (this._yaxis.min <= 0 && this._yaxis.max >= 0) {
+                                ystart = this._yaxis.series_u2p(0);
+                            }
+                            else if (this._yaxis.min > 0) {
+                                ystart = ctx.canvas.height;
+                            }
+                            else {
+                                ystart = 0;
+                            }
+                        }
+                        else {
+                            ystart = ctx.canvas.height;
+                        }
+                    }
+                    if ((this.fillToZero && this._plotData[i][1] < 0) || (this.waterfall && this._data[i][1] < 0)) {
+                        if (this.varyBarColor && !this._stack) {
+                            if (this.useNegativeColors) {
+                                opts.fillStyle = negativeColors.next();
+                            }
+                            else {
+                                opts.fillStyle = positiveColors.next();
+                            }
+                        }
+                        else {
+                            opts.fillStyle = negativeColor;
+                        }
+                    }
+                    else {
+                        if (this.varyBarColor && !this._stack) {
+                            opts.fillStyle = positiveColors.next();
+                        }
+                        else {
+                            opts.fillStyle = positiveColor;
+                        }
+                    }
+                    
+                    if (!this.fillToZero || this._plotData[i][1] >= 0) { 
+                        points.push([base-this.barWidth/2, ystart]);
+                        points.push([base-this.barWidth/2, gridData[i][1]]);
+                        points.push([base+this.barWidth/2, gridData[i][1]]);
+                        points.push([base+this.barWidth/2, ystart]);
+                    }
+                    // for negative bars make sure points are always ordered clockwise
+                    else {              
+                        points.push([base-this.barWidth/2, gridData[i][1]]);
+                        points.push([base-this.barWidth/2, ystart]);
+                        points.push([base+this.barWidth/2, ystart]);
+                        points.push([base+this.barWidth/2, gridData[i][1]]);
+                    }
+                    this._barPoints.push(points);
+                    // now draw the shadows if not stacked.
+                    // for stacked plots, they are predrawn by drawShadow
+                    if (shadow && !this._stack) {
+                        var sopts = $.extend(true, {}, opts);
+                        // need to get rid of fillStyle on shadow.
+                        delete sopts.fillStyle;
+                        this.renderer.shadowRenderer.draw(ctx, points, sopts);
+                    }
+                    var clr = opts.fillStyle || this.color;
+                    this._dataColors.push(clr);
+                    this.renderer.shapeRenderer.draw(ctx, points, opts); 
+                }
+            }
+            
+            else if (this.barDirection == 'horizontal'){
+                for (var i=0; i<gridData.length; i++) {
+                    if (!this._stack && this.data[i][0] == null) {
+                        continue;
+                    }
+                    points = [];
+                    base = gridData[i][1] - this._barNudge;
+                    xstart;
+                    
+                    if (this._stack && this._prevGridData.length) {
+                        xstart = getStart(this.index, i, this._plotData[i][0], plot, 'x');
+                    }
+                    // not stacked
+                    else {
+                        if (this.fillToZero) {
+                            xstart = this._xaxis.series_u2p(0);
+                        }
+                        else if (this.waterfall && i > 0 && i < this.gridData.length-1) {
+                            xstart = this.gridData[i-1][0];
+                        }
+                        else if (this.waterfall && i == 0 && i < this.gridData.length-1) {
+                            if (this._xaxis.min <= 0 && this._xaxis.max >= 0) {
+                                xstart = this._xaxis.series_u2p(0);
+                            }
+                            else if (this._xaxis.min > 0) {
+                                xstart = 0;
+                            }
+                            else {
+                                xstart = 0;
+                            }
+                        }
+                        else if (this.waterfall && i == this.gridData.length - 1) {
+                            if (this._xaxis.min <= 0 && this._xaxis.max >= 0) {
+                                xstart = this._xaxis.series_u2p(0);
+                            }
+                            else if (this._xaxis.min > 0) {
+                                xstart = 0;
+                            }
+                            else {
+                                xstart = ctx.canvas.width;
+                            }
+                        }
+                        else {
+                            xstart = 0;
+                        }
+                    }
+                    if ((this.fillToZero && this._plotData[i][0] < 0) || (this.waterfall && this._data[i][0] < 0)) {
+                        if (this.varyBarColor && !this._stack) {
+                            if (this.useNegativeColors) {
+                                opts.fillStyle = negativeColors.next();
+                            }
+                            else {
+                                opts.fillStyle = positiveColors.next();
+                            }
+                        }
+                        else {
+                            opts.fillStyle = negativeColor;
+                        }
+                    }
+                    else {
+                        if (this.varyBarColor && !this._stack) {
+                            opts.fillStyle = positiveColors.next();
+                        }
+                        else {
+                            opts.fillStyle = positiveColor;
+                        }                    
+                    }
+                    
+
+                    if (!this.fillToZero || this._plotData[i][0] >= 0) {
+                        points.push([xstart, base + this.barWidth / 2]);
+                        points.push([xstart, base - this.barWidth / 2]);
+                        points.push([gridData[i][0], base - this.barWidth / 2]);
+                        points.push([gridData[i][0], base + this.barWidth / 2]);
+                    }
+                    else {
+                        points.push([gridData[i][0], base + this.barWidth / 2]);
+                        points.push([gridData[i][0], base - this.barWidth / 2]);
+                        points.push([xstart, base - this.barWidth / 2]);
+                        points.push([xstart, base + this.barWidth / 2]);
+                    }
+
+                    this._barPoints.push(points);
+                    // now draw the shadows if not stacked.
+                    // for stacked plots, they are predrawn by drawShadow
+                    if (shadow && !this._stack) {
+                        var sopts = $.extend(true, {}, opts);
+                        delete sopts.fillStyle;
+                        this.renderer.shadowRenderer.draw(ctx, points, sopts);
+                    }
+                    var clr = opts.fillStyle || this.color;
+                    this._dataColors.push(clr);
+                    this.renderer.shapeRenderer.draw(ctx, points, opts);
+                } 
+            }
+        }                
+        
+        if (this.highlightColors.length == 0) {
+            this.highlightColors = $.jqplot.computeHighlightColors(this._dataColors);
+        }
+        
+        else if (typeof(this.highlightColors) == 'string') {
+            var temp = this.highlightColors;
+            this.highlightColors = [];
+            for (var i=0; i<this._dataColors.length; i++) {
+                this.highlightColors.push(temp);
+            }
+        }
+        
+    };
+    
+     
+    // for stacked plots, shadows will be pre drawn by drawShadow.
+    $.jqplot.BarRenderer.prototype.drawShadow = function(ctx, gridData, options, plot) {
+        var i;
+        var opts = (options != undefined) ? options : {};
+        var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+        var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
+        var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+        var xaxis = this.xaxis;
+        var yaxis = this.yaxis;
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        var pointx, points, pointy, nvals, nseries, pos;
+        
+        if (this._stack && this.shadow) {
+            if (this.barWidth == null) {
+                this.renderer.setBarWidth.call(this);
+            }
+        
+            var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
+            nvals = temp[0];
+            nseries = temp[1];
+            pos = temp[2];
+        
+            if (this._stack) {
+                this._barNudge = 0;
+            }
+            else {
+                this._barNudge = (-Math.abs(nseries/2 - 0.5) + pos) * (this.barWidth + this.barPadding);
+            }
+            if (showLine) {
+            
+                if (this.barDirection == 'vertical') {
+                    for (var i=0; i<gridData.length; i++) {
+                        if (this.data[i][1] == null) {
+                            continue;
+                        }
+                        points = [];
+                        var base = gridData[i][0] + this._barNudge;
+                        var ystart;
+                    
+                        if (this._stack && this._prevGridData.length) {
+                            ystart = getStart(this.index, i, this._plotData[i][1], plot, 'y');
+                        }
+                        else {
+                            if (this.fillToZero) {
+                                ystart = this._yaxis.series_u2p(0);
+                            }
+                            else {
+                                ystart = ctx.canvas.height;
+                            }
+                        }
+                    
+                        points.push([base-this.barWidth/2, ystart]);
+                        points.push([base-this.barWidth/2, gridData[i][1]]);
+                        points.push([base+this.barWidth/2, gridData[i][1]]);
+                        points.push([base+this.barWidth/2, ystart]);
+                        this.renderer.shadowRenderer.draw(ctx, points, opts);
+                    }
+                }
+            
+                else if (this.barDirection == 'horizontal'){
+                    for (var i=0; i<gridData.length; i++) {
+                        if (this.data[i][0] == null) {
+                            continue;
+                        }
+                        points = [];
+                        var base = gridData[i][1] - this._barNudge;
+                        var xstart;
+                    
+                        if (this._stack && this._prevGridData.length) {
+                            xstart = getStart(this.index, i, this._plotData[i][0], plot, 'x');
+                        }
+                        else {
+                            if (this.fillToZero) {
+                                xstart = this._xaxis.series_u2p(0);
+                            }
+                            else {
+                                xstart = 0;
+                            }
+                        }
+                    
+                        points.push([xstart, base+this.barWidth/2]);
+                        points.push([gridData[i][0], base+this.barWidth/2]);
+                        points.push([gridData[i][0], base-this.barWidth/2]);
+                        points.push([xstart, base-this.barWidth/2]);
+                        this.renderer.shadowRenderer.draw(ctx, points, opts);
+                    }  
+                }
+            }   
+            
+        }
+    };
+    
+    function postInit(target, data, options) {
+        for (var i=0; i<this.series.length; i++) {
+            if (this.series[i].renderer.constructor == $.jqplot.BarRenderer) {
+                // don't allow mouseover and mousedown at same time.
+                if (this.series[i].highlightMouseOver) {
+                    this.series[i].highlightMouseDown = false;
+                }
+            }
+        }
+    }
+    
+    // called within context of plot
+    // create a canvas which we can draw on.
+    // insert it before the eventCanvas, so eventCanvas will still capture events.
+    function postPlotDraw() {
+        // Memory Leaks patch    
+        if (this.plugins.barRenderer && this.plugins.barRenderer.highlightCanvas) {
+
+            this.plugins.barRenderer.highlightCanvas.resetCanvas();
+            this.plugins.barRenderer.highlightCanvas = null;
+        }
+         
+        this.plugins.barRenderer = {highlightedSeriesIndex:null};
+        this.plugins.barRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
+        
+        this.eventCanvas._elem.before(this.plugins.barRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-barRenderer-highlight-canvas', this._plotDimensions, this));
+        this.plugins.barRenderer.highlightCanvas.setContext();
+        this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
+    }   
+    
+    function highlight (plot, sidx, pidx, points) {
+        var s = plot.series[sidx];
+        var canvas = plot.plugins.barRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        s._highlightedPoint = pidx;
+        plot.plugins.barRenderer.highlightedSeriesIndex = sidx;
+        var opts = {fillStyle: s.highlightColors[pidx]};
+        s.renderer.shapeRenderer.draw(canvas._ctx, points, opts);
+        canvas = null;
+    }
+    
+    function unhighlight (plot) {
+        var canvas = plot.plugins.barRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        for (var i=0; i<plot.series.length; i++) {
+            plot.series[i]._highlightedPoint = null;
+        }
+        plot.plugins.barRenderer.highlightedSeriesIndex = null;
+        plot.target.trigger('jqplotDataUnhighlight');
+        canvas =  null;
+    }
+    
+    
+    function handleMove(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var evt1 = jQuery.Event('jqplotDataMouseOver');
+            evt1.pageX = ev.pageX;
+            evt1.pageY = ev.pageY;
+            plot.target.trigger(evt1, ins);
+            if (plot.series[ins[0]].show && plot.series[ins[0]].highlightMouseOver &&
+                !(ins[0] == plot.plugins.barRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    }
+    
+    function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.barRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    }
+    
+    function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
+        var idx = plot.plugins.barRenderer.highlightedSeriesIndex;
+        if (idx != null && plot.series[idx].highlightMouseDown) {
+            unhighlight(plot);
+        }
+    }
+    
+    function handleClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var evt = jQuery.Event('jqplotDataClick');
+            evt.which = ev.which;
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            plot.target.trigger(evt, ins);
+        }
+    }
+    
+    function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var idx = plot.plugins.barRenderer.highlightedSeriesIndex;
+            if (idx != null && plot.series[idx].highlightMouseDown) {
+                unhighlight(plot);
+            }
+            var evt = jQuery.Event('jqplotDataRightClick');
+            evt.which = ev.which;
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            plot.target.trigger(evt, ins);
+        }
+    }
+    
+    
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.blockRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.blockRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.blockRenderer.js	(revision 2119)
@@ -0,0 +1,235 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    /**
+     * Class: $.jqplot.BlockRenderer
+     * Plugin renderer to draw a x-y block chart.  A Block chart has data points displayed as
+     * colored squares with a text label inside.  Data must be supplied in the form:
+     * 
+     * > [[x1, y1, "label 1", {css}], [x2, y2, "label 2", {css}], ...]
+     * 
+     * The label and css object are optional.  If the label is ommitted, the
+     * box will collapse unless a css height and/or width is specified.
+     * 
+     * The css object is an object specifying css properties 
+     * such as:
+     * 
+     * > {background:'#4f98a5', border:'3px solid gray', padding:'1px'}
+     * 
+     * Note that css properties specified with the data point override defaults
+     * specified with the series.
+     * 
+     */
+    $.jqplot.BlockRenderer = function(){
+        $.jqplot.LineRenderer.call(this);
+    };
+    
+    $.jqplot.BlockRenderer.prototype = new $.jqplot.LineRenderer();
+    $.jqplot.BlockRenderer.prototype.constructor = $.jqplot.BlockRenderer;
+    
+    // called with scope of a series
+    $.jqplot.BlockRenderer.prototype.init = function(options) {
+        // Group: Properties
+        //
+        // prop: css
+        // default css styles that will be applied to all data blocks.
+        // these values will be overridden by css styles supplied with the
+        // individulal data points.
+        this.css = {padding:'2px', border:'1px solid #999', textAlign:'center'};
+        // prop: escapeHtml
+        // true to escape html in the box label.
+        this.escapeHtml = false;
+        // prop: insertBreaks
+        // true to turn spaces in data block label into html breaks <br />.
+        this.insertBreaks = true;
+        // prop: varyBlockColors
+        // true to vary the color of each block in this series according to
+        // the seriesColors array.  False to set each block to the color
+        // specified on this series.  This has no effect if a css background color
+        // option is specified in the renderer css options.
+        this.varyBlockColors = false;
+        $.extend(true, this, options);
+        if (this.css.backgroundColor) {
+            this.color = this.css.backgroundColor;
+        }
+        else if (this.css.background) {
+            this.color = this.css.background;
+        }
+        else if (!this.varyBlockColors) {
+            this.css.background = this.color;
+        }
+        this.canvas = new $.jqplot.BlockCanvas();
+        this.shadowCanvas =  new $.jqplot.BlockCanvas();
+        this.canvas._plotDimensions = this._plotDimensions;
+        this.shadowCanvas._plotDimensions = this._plotDimensions;
+        this._type = 'block';
+        
+        // group: Methods 
+        //
+        // Method: moveBlock
+        // Moves an individual block.  More efficient than redrawing
+        // the whole series by calling plot.drawSeries().
+        // Properties:
+        // idx - the 0 based index of the block or point in this series.
+        // x - the x coordinate in data units (value on x axis) to move the block to.
+        // y - the y coordinate in data units (value on the y axis) to move the block to.
+        // duration - optional parameter to create an animated movement.  Can be a
+        // number (higher is slower animation) or 'fast', 'normal' or 'slow'.  If not
+        // provided, the element is moved without any animation.
+        this.moveBlock = function (idx, x, y, duration) {
+            // update plotData, stackData, data and gridData
+            // x and y are in data coordinates.
+            var el = this.canvas._elem.children(':eq('+idx+')');
+            this.data[idx][0] = x;
+            this.data[idx][1] = y;
+            this._plotData[idx][0] = x;
+            this._plotData[idx][1] = y;
+            this._stackData[idx][0] = x;
+            this._stackData[idx][1] = y;
+            this.gridData[idx][0] = this._xaxis.series_u2p(x);
+            this.gridData[idx][1] = this._yaxis.series_u2p(y);
+            var w = el.outerWidth();
+            var h = el.outerHeight();
+            var left = this.gridData[idx][0] - w/2 + 'px';
+            var top = this.gridData[idx][1] - h/2 + 'px';
+            if (duration) {
+                if (parseInt(duration, 10)) {
+                    duration = parseInt(duration, 10);
+                }
+                el.animate({left:left, top:top}, duration);
+            }
+            else {
+                el.css({left:left, top:top});
+            }
+            el = null;
+        };
+    };
+    
+    // called with scope of series
+    $.jqplot.BlockRenderer.prototype.draw = function (ctx, gd, options) {
+        if (this.plugins.pointLabels) {
+            this.plugins.pointLabels.show = false;
+        }
+        var i, el, d, gd, t, css, w, h, left, top;
+        var opts = (options != undefined) ? options : {};
+        var colorGenerator = new $.jqplot.ColorGenerator(this.seriesColors);
+        this.canvas._elem.empty();
+        for (i=0; i<this.gridData.length; i++) {
+            d = this.data[i];
+            gd = this.gridData[i];
+            t = '';
+            css = {};
+            if (typeof d[2] == 'string') {
+                t = d[2];
+            }
+            else if (typeof d[2] == 'object') {
+                css = d[2];
+            }
+            if (typeof d[3] ==  'object') {
+                css = d[3];
+            }
+            if (this.insertBreaks){ 
+                t = t.replace(/ /g, '<br />');
+            }
+            css = $.extend(true, {}, this.css, css);
+            // create a div
+            el = $('<div style="position:absolute;margin-left:auto;margin-right:auto;"></div>');
+            this.canvas._elem.append(el);
+            // set text
+            this.escapeHtml ? el.text(t) : el.html(t);
+            // style it
+            // remove styles we don't want overridden.
+            delete css.position;
+            delete css.marginRight;
+            delete css.marginLeft;
+            if (!css.background && !css.backgroundColor && !css.backgroundImage){ 
+                css.background = colorGenerator.next();
+            }
+            el.css(css);
+            w = el.outerWidth();
+            h = el.outerHeight();
+            left = gd[0] - w/2 + 'px';
+            top = gd[1] - h/2 + 'px';
+            el.css({left:left, top:top});
+            el = null;
+        }
+    };
+    
+    $.jqplot.BlockCanvas = function() {
+        $.jqplot.ElemContainer.call(this);
+        this._ctx;  
+    };
+    
+    $.jqplot.BlockCanvas.prototype = new $.jqplot.ElemContainer();
+    $.jqplot.BlockCanvas.prototype.constructor = $.jqplot.BlockCanvas;
+    
+    $.jqplot.BlockCanvas.prototype.createElement = function(offsets, clss, plotDimensions) {
+        this._offsets = offsets;
+        var klass = 'jqplot-blockCanvas';
+        if (clss != undefined) {
+            klass = clss;
+        }
+        var elem;
+        // if this canvas already has a dom element, don't make a new one.
+        if (this._elem) {
+            elem = this._elem.get(0);
+        }
+        else {
+            elem = document.createElement('div');
+        }
+        // if new plotDimensions supplied, use them.
+        if (plotDimensions != undefined) {
+            this._plotDimensions = plotDimensions;
+        }
+        
+        var w = this._plotDimensions.width - this._offsets.left - this._offsets.right + 'px';
+        var h = this._plotDimensions.height - this._offsets.top - this._offsets.bottom + 'px';
+        this._elem = $(elem);
+        this._elem.css({ position: 'absolute', width:w, height:h, left: this._offsets.left, top: this._offsets.top });
+        
+        this._elem.addClass(klass);
+        return this._elem;
+    };
+    
+    $.jqplot.BlockCanvas.prototype.setContext = function() {
+        this._ctx = {
+            canvas:{
+                width:0,
+                height:0
+            },
+            clearRect:function(){return null;}
+        };
+        return this._ctx;
+    };
+    
+})(jQuery);
+    
+    
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.bubbleRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.bubbleRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.bubbleRenderer.js	(revision 2119)
@@ -0,0 +1,759 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    var arrayMax = function( array ){
+        return Math.max.apply( Math, array );
+    };
+    var arrayMin = function( array ){
+        return Math.min.apply( Math, array );
+    };
+
+    /**
+     * Class: $.jqplot.BubbleRenderer
+     * Plugin renderer to draw a bubble chart.  A Bubble chart has data points displayed as
+     * colored circles with an optional text label inside.  To use
+     * the bubble renderer, you must include the bubble renderer like:
+     * 
+     * > <script language="javascript" type="text/javascript" src="../src/plugins/jqplot.bubbleRenderer.js"></script>
+     * 
+     * Data must be supplied in 
+     * the form:
+     * 
+     * > [[x1, y1, r1, <label or {label:'text', color:color}>], ...]
+     * 
+     * where the label or options 
+     * object is optional.  
+     * 
+     * Note that all bubble colors will be the same
+     * unless the "varyBubbleColors" option is set to true.  Colors can be specified in the data array
+     * or in the seriesColors array option on the series.  If no colors are defined, the default jqPlot
+     * series of 16 colors are used.  Colors are automatically cycled around again if there are more
+     * bubbles than colors.
+     * 
+     * Bubbles are autoscaled by default to fit within the chart area while maintaining 
+     * relative sizes.  If the "autoscaleBubbles" option is set to false, the r(adius) values
+     * in the data array a treated as literal pixel values for the radii of the bubbles.
+     * 
+     * Properties are passed into the bubble renderer in the rendererOptions object of
+     * the series options like:
+     * 
+     * > seriesDefaults: {
+     * >     renderer: $.jqplot.BubbleRenderer,
+     * >     rendererOptions: {
+     * >         bubbleAlpha: 0.7,
+     * >         varyBubbleColors: false
+     * >     }
+     * > }
+     * 
+     */
+    $.jqplot.BubbleRenderer = function(){
+        $.jqplot.LineRenderer.call(this);
+    };
+    
+    $.jqplot.BubbleRenderer.prototype = new $.jqplot.LineRenderer();
+    $.jqplot.BubbleRenderer.prototype.constructor = $.jqplot.BubbleRenderer;
+    
+    // called with scope of a series
+    $.jqplot.BubbleRenderer.prototype.init = function(options, plot) {
+        // Group: Properties
+        //
+        // prop: varyBubbleColors
+        // True to vary the color of each bubble in this series according to
+        // the seriesColors array.  False to set each bubble to the color
+        // specified on this series.  This has no effect if a css background color
+        // option is specified in the renderer css options.
+        this.varyBubbleColors = true;
+        // prop: autoscaleBubbles
+        // True to scale the bubble radius based on plot size.
+        // False will use the radius value as provided as a raw pixel value for
+        // bubble radius.
+        this.autoscaleBubbles = true;
+        // prop: autoscaleMultiplier
+        // Multiplier the bubble size if autoscaleBubbles is true.
+        this.autoscaleMultiplier = 1.0;
+        // prop: autoscalePointsFactor
+        // Factor which decreases bubble size based on how many bubbles on on the chart.
+        // 0 means no adjustment for number of bubbles.  Negative values will decrease
+        // size of bubbles as more bubbles are added.  Values between 0 and -0.2
+        // should work well.
+        this.autoscalePointsFactor = -0.07;
+        // prop: escapeHtml
+        // True to escape html in bubble label text.
+        this.escapeHtml = true;
+        // prop: highlightMouseOver
+        // True to highlight bubbles when moused over.
+        // This must be false to enable highlightMouseDown to highlight when clicking on a slice.
+        this.highlightMouseOver = true;
+        // prop: highlightMouseDown
+        // True to highlight when a mouse button is pressed over a bubble.
+        // This will be disabled if highlightMouseOver is true.
+        this.highlightMouseDown = false;
+        // prop: highlightColors
+        // An array of colors to use when highlighting a slice.  Calculated automatically
+        // if not supplied.
+        this.highlightColors = [];
+        // prop: bubbleAlpha
+        // Alpha transparency to apply to all bubbles in this series.
+        this.bubbleAlpha = 1.0;
+        // prop: highlightAlpha
+        // Alpha transparency to apply when highlighting bubble.
+        // Set to value of bubbleAlpha by default.
+        this.highlightAlpha = null;
+        // prop: bubbleGradients
+        // True to color the bubbles with gradient fills instead of flat colors.
+        // NOT AVAILABLE IN IE due to lack of excanvas support for radial gradient fills.
+        // will be ignored in IE.
+        this.bubbleGradients = false;
+        // prop: showLabels
+        // True to show labels on bubbles (if any), false to not show.
+        this.showLabels = true;
+        // array of [point index, radius] which will be sorted in descending order to plot 
+        // largest points below smaller points.
+        this.radii = [];
+        this.maxRadius = 0;
+        // index of the currenty highlighted point, if any
+        this._highlightedPoint = null;
+        // array of jQuery labels.
+        this.labels = [];
+        this.bubbleCanvases = [];
+        this._type = 'bubble';
+        
+        // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
+        if (options.highlightMouseDown && options.highlightMouseOver == null) {
+            options.highlightMouseOver = false;
+        }
+        
+        $.extend(true, this, options);
+        
+        if (this.highlightAlpha == null) {
+            this.highlightAlpha = this.bubbleAlpha;
+            if (this.bubbleGradients) {
+                this.highlightAlpha = 0.35;
+            }
+        }
+        
+        this.autoscaleMultiplier = this.autoscaleMultiplier * Math.pow(this.data.length, this.autoscalePointsFactor);
+        
+        // index of the currenty highlighted point, if any
+        this._highlightedPoint = null;
+        
+        // adjust the series colors for options colors passed in with data or for alpha.
+        // note, this can leave undefined holes in the seriesColors array.
+        var comps;
+        for (var i=0; i<this.data.length; i++) {
+            var color = null;
+            var d = this.data[i];
+            this.maxRadius = Math.max(this.maxRadius, d[2]);
+            if (d[3]) {
+                if (typeof(d[3]) == 'object') {
+                    color = d[3]['color'];
+                }
+            }
+            
+            if (color == null) {
+                if (this.seriesColors[i] != null) {
+                    color = this.seriesColors[i];
+                }
+            }
+            
+            if (color && this.bubbleAlpha < 1.0) {
+                comps = $.jqplot.getColorComponents(color);
+                color = 'rgba('+comps[0]+', '+comps[1]+', '+comps[2]+', '+this.bubbleAlpha+')';
+            }
+            
+            if (color) {
+                this.seriesColors[i] = color;
+            }
+        }
+        
+        if (!this.varyBubbleColors) {
+            this.seriesColors = [this.color];
+        }
+        
+        this.colorGenerator = new $.jqplot.ColorGenerator(this.seriesColors);
+        
+        // set highlight colors if none provided
+        if (this.highlightColors.length == 0) {
+            for (var i=0; i<this.seriesColors.length; i++){
+                var rgba = $.jqplot.getColorComponents(this.seriesColors[i]);
+                var newrgb = [rgba[0], rgba[1], rgba[2]];
+                var sum = newrgb[0] + newrgb[1] + newrgb[2];
+                for (var j=0; j<3; j++) {
+                    // when darkening, lowest color component can be is 60.
+                    newrgb[j] = (sum > 570) ?  newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
+                    newrgb[j] = parseInt(newrgb[j], 10);
+                }
+                this.highlightColors.push('rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+', '+this.highlightAlpha+')');
+            }
+        }
+        
+        this.highlightColorGenerator = new $.jqplot.ColorGenerator(this.highlightColors);
+        
+        var sopts = {fill:true, isarc:true, angle:this.shadowAngle, alpha:this.shadowAlpha, closePath:true};
+        
+        this.renderer.shadowRenderer.init(sopts);
+        
+        this.canvas = new $.jqplot.DivCanvas();
+        this.canvas._plotDimensions = this._plotDimensions;
+        
+        plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
+        plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
+        plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
+        plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
+        plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
+        plot.postDrawHooks.addOnce(postPlotDraw);
+        
+    };
+    
+
+    // converts the user data values to grid coordinates and stores them
+    // in the gridData array.
+    // Called with scope of a series.
+    $.jqplot.BubbleRenderer.prototype.setGridData = function(plot) {
+        // recalculate the grid data
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        var data = this._plotData;
+        this.gridData = [];
+        var radii = [];
+        this.radii = [];
+        var dim = Math.min(plot._height, plot._width);
+        for (var i=0; i<this.data.length; i++) {
+            if (data[i] != null) {
+                this.gridData.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1]), data[i][2]]);
+                this.radii.push([i, data[i][2]]);
+                radii.push(data[i][2]);
+            }
+        }
+        var r, val, maxr = this.maxRadius = arrayMax(radii);
+        var l = this.gridData.length;
+        if (this.autoscaleBubbles) {
+            for (var i=0; i<l; i++) {
+                val = radii[i]/maxr;
+                r = this.autoscaleMultiplier * dim / 6;
+                this.gridData[i][2] = r * val;
+            }
+        }
+        
+        this.radii.sort(function(a, b) { return b[1] - a[1]; });
+    };
+    
+    // converts any arbitrary data values to grid coordinates and
+    // returns them.  This method exists so that plugins can use a series'
+    // linerenderer to generate grid data points without overwriting the
+    // grid data associated with that series.
+    // Called with scope of a series.
+    $.jqplot.BubbleRenderer.prototype.makeGridData = function(data, plot) {
+        // recalculate the grid data
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        var gd = [];
+        var radii = [];
+        this.radii = [];
+        var dim = Math.min(plot._height, plot._width);
+        for (var i=0; i<data.length; i++) {
+            if (data[i] != null) {
+                gd.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1]), data[i][2]]);
+                radii.push(data[i][2]);
+                this.radii.push([i, data[i][2]]);
+            }
+        }
+        var r, val, maxr = this.maxRadius = arrayMax(radii);
+        var l = this.gridData.length;
+        if (this.autoscaleBubbles) {
+            for (var i=0; i<l; i++) {
+                val = radii[i]/maxr;
+                r = this.autoscaleMultiplier * dim / 6;
+                gd[i][2] = r * val;
+            }
+        }
+        this.radii.sort(function(a, b) { return b[1] - a[1]; });
+        return gd;
+    };
+    
+    // called with scope of series
+    $.jqplot.BubbleRenderer.prototype.draw = function (ctx, gd, options) {
+        if (this.plugins.pointLabels) {
+            this.plugins.pointLabels.show = false;
+        }
+        var opts = (options != undefined) ? options : {};
+        var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+        this.canvas._elem.empty();
+        for (var i=0; i<this.radii.length; i++) {
+            var idx = this.radii[i][0];
+            var t=null;
+            var color = null;
+            var el = null;
+            var tel = null;
+            var d = this.data[idx];
+            var gd = this.gridData[idx];
+            if (d[3]) {
+                if (typeof(d[3]) == 'object') {
+                    t = d[3]['label'];
+                }
+                else if (typeof(d[3]) == 'string') {
+                    t = d[3];
+                }
+            }
+            
+            // color = (this.varyBubbleColors) ? this.colorGenerator.get(idx) : this.color;
+            color = this.colorGenerator.get(idx);
+            
+            // If we're drawing a shadow, expand the canvas dimensions to accomodate.
+            var canvasRadius = gd[2];
+            var offset, depth;
+            if (this.shadow) {
+                offset = (0.7 + gd[2]/40).toFixed(1);
+                depth = 1 + Math.ceil(gd[2]/15);
+                canvasRadius += offset*depth;
+            }
+            this.bubbleCanvases[idx] = new $.jqplot.BubbleCanvas();
+            this.canvas._elem.append(this.bubbleCanvases[idx].createElement(gd[0], gd[1], canvasRadius));
+            this.bubbleCanvases[idx].setContext();
+            var ctx = this.bubbleCanvases[idx]._ctx;
+            var x = ctx.canvas.width/2;
+            var y = ctx.canvas.height/2;
+            if (this.shadow) {
+                this.renderer.shadowRenderer.draw(ctx, [x, y, gd[2], 0, 2*Math.PI], {offset: offset, depth: depth});
+            }
+            this.bubbleCanvases[idx].draw(gd[2], color, this.bubbleGradients, this.shadowAngle/180*Math.PI);
+            
+            // now draw label.
+            if (t && this.showLabels) {
+                tel = $('<div style="position:absolute;" class="jqplot-bubble-label"></div>');
+                if (this.escapeHtml) {
+                    tel.text(t);
+                }
+                else {
+                    tel.html(t);
+                }
+                this.canvas._elem.append(tel);
+                var h = $(tel).outerHeight();
+                var w = $(tel).outerWidth();
+                var top = gd[1] - 0.5*h;
+                var left = gd[0] - 0.5*w;
+                tel.css({top: top, left: left});
+                this.labels[idx] = $(tel);
+            }
+        }
+    };
+
+    
+    $.jqplot.DivCanvas = function() {
+        $.jqplot.ElemContainer.call(this);
+        this._ctx;  
+    };
+    
+    $.jqplot.DivCanvas.prototype = new $.jqplot.ElemContainer();
+    $.jqplot.DivCanvas.prototype.constructor = $.jqplot.DivCanvas;
+    
+    $.jqplot.DivCanvas.prototype.createElement = function(offsets, clss, plotDimensions) {
+        this._offsets = offsets;
+        var klass = 'jqplot-DivCanvas';
+        if (clss != undefined) {
+            klass = clss;
+        }
+        var elem;
+        // if this canvas already has a dom element, don't make a new one.
+        if (this._elem) {
+            elem = this._elem.get(0);
+        }
+        else {
+            elem = document.createElement('div');
+        }
+        // if new plotDimensions supplied, use them.
+        if (plotDimensions != undefined) {
+            this._plotDimensions = plotDimensions;
+        }
+        
+        var w = this._plotDimensions.width - this._offsets.left - this._offsets.right + 'px';
+        var h = this._plotDimensions.height - this._offsets.top - this._offsets.bottom + 'px';
+        this._elem = $(elem);
+        this._elem.css({ position: 'absolute', width:w, height:h, left: this._offsets.left, top: this._offsets.top });
+        
+        this._elem.addClass(klass);
+        return this._elem;
+    };
+    
+    $.jqplot.DivCanvas.prototype.setContext = function() {
+        this._ctx = {
+            canvas:{
+                width:0,
+                height:0
+            },
+            clearRect:function(){return null;}
+        };
+        return this._ctx;
+    };
+    
+    $.jqplot.BubbleCanvas = function() {
+        $.jqplot.ElemContainer.call(this);
+        this._ctx;
+    };
+    
+    $.jqplot.BubbleCanvas.prototype = new $.jqplot.ElemContainer();
+    $.jqplot.BubbleCanvas.prototype.constructor = $.jqplot.BubbleCanvas;
+    
+    // initialize with the x,y pont of bubble center and the bubble radius.
+    $.jqplot.BubbleCanvas.prototype.createElement = function(x, y, r) {     
+        var klass = 'jqplot-bubble-point';
+
+        var elem;
+        // if this canvas already has a dom element, don't make a new one.
+        if (this._elem) {
+            elem = this._elem.get(0);
+        }
+        else {
+            elem = document.createElement('canvas');
+        }
+        
+        elem.width = (r != null) ? 2*r : elem.width;
+        elem.height = (r != null) ? 2*r : elem.height;
+        this._elem = $(elem);
+        var l = (x != null && r != null) ? x - r : this._elem.css('left');
+        var t = (y != null && r != null) ? y - r : this._elem.css('top');
+        this._elem.css({ position: 'absolute', left: l, top: t });
+        
+        this._elem.addClass(klass);
+        if ($.jqplot.use_excanvas) {
+            window.G_vmlCanvasManager.init_(document);
+            elem = window.G_vmlCanvasManager.initElement(elem);
+        }
+        
+        return this._elem;
+    };
+    
+    $.jqplot.BubbleCanvas.prototype.draw = function(r, color, gradients, angle) {
+        var ctx = this._ctx;
+        // r = Math.floor(r*1.04);
+        // var x = Math.round(ctx.canvas.width/2);
+        // var y = Math.round(ctx.canvas.height/2);
+        var x = ctx.canvas.width/2;
+        var y = ctx.canvas.height/2;
+        ctx.save();
+        if (gradients && !$.jqplot.use_excanvas) {
+            r = r*1.04;
+            var comps = $.jqplot.getColorComponents(color);
+            var colorinner = 'rgba('+Math.round(comps[0]+0.8*(255-comps[0]))+', '+Math.round(comps[1]+0.8*(255-comps[1]))+', '+Math.round(comps[2]+0.8*(255-comps[2]))+', '+comps[3]+')';
+            var colorend = 'rgba('+comps[0]+', '+comps[1]+', '+comps[2]+', 0)';
+            // var rinner = Math.round(0.35 * r);
+            // var xinner = Math.round(x - Math.cos(angle) * 0.33 * r);
+            // var yinner = Math.round(y - Math.sin(angle) * 0.33 * r);
+            var rinner = 0.35 * r;
+            var xinner = x - Math.cos(angle) * 0.33 * r;
+            var yinner = y - Math.sin(angle) * 0.33 * r;
+            var radgrad = ctx.createRadialGradient(xinner, yinner, rinner, x, y, r);
+            radgrad.addColorStop(0, colorinner);
+            radgrad.addColorStop(0.93, color);
+            radgrad.addColorStop(0.96, colorend);
+            radgrad.addColorStop(1, colorend);
+            // radgrad.addColorStop(.98, colorend);
+            ctx.fillStyle = radgrad;
+            ctx.fillRect(0,0, ctx.canvas.width, ctx.canvas.height);
+        }
+        else {
+            ctx.fillStyle = color;
+            ctx.strokeStyle = color;
+            ctx.lineWidth = 1;
+            ctx.beginPath();
+            var ang = 2*Math.PI;
+            ctx.arc(x, y, r, 0, ang, 0);
+            ctx.closePath();
+            ctx.fill();
+        }
+        ctx.restore();
+    };
+    
+    $.jqplot.BubbleCanvas.prototype.setContext = function() {
+        this._ctx = this._elem.get(0).getContext("2d");
+        return this._ctx;
+    };
+    
+    $.jqplot.BubbleAxisRenderer = function() {
+        $.jqplot.LinearAxisRenderer.call(this);
+    };
+    
+    $.jqplot.BubbleAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+    $.jqplot.BubbleAxisRenderer.prototype.constructor = $.jqplot.BubbleAxisRenderer;
+        
+    // called with scope of axis object.
+    $.jqplot.BubbleAxisRenderer.prototype.init = function(options){
+        $.extend(true, this, options);
+        var db = this._dataBounds;
+        var minsidx = 0,
+            minpidx = 0,
+            maxsidx = 0,
+            maxpidx = 0,
+            maxr = 0,
+            minr = 0,
+            minMaxRadius = 0,
+            maxMaxRadius = 0,
+            maxMult = 0,
+            minMult = 0;
+        // Go through all the series attached to this axis and find
+        // the min/max bounds for this axis.
+        for (var i=0; i<this._series.length; i++) {
+            var s = this._series[i];
+            var d = s._plotData;
+            
+            for (var j=0; j<d.length; j++) { 
+                if (this.name == 'xaxis' || this.name == 'x2axis') {
+                    if (d[j][0] < db.min || db.min == null) {
+                        db.min = d[j][0];
+                        minsidx=i;
+                        minpidx=j;
+                        minr = d[j][2];
+                        minMaxRadius = s.maxRadius;
+                        minMult = s.autoscaleMultiplier;
+                    }
+                    if (d[j][0] > db.max || db.max == null) {
+                        db.max = d[j][0];
+                        maxsidx=i;
+                        maxpidx=j;
+                        maxr = d[j][2];
+                        maxMaxRadius = s.maxRadius;
+                        maxMult = s.autoscaleMultiplier;
+                    }
+                }              
+                else {
+                    if (d[j][1] < db.min || db.min == null) {
+                        db.min = d[j][1];
+                        minsidx=i;
+                        minpidx=j;
+                        minr = d[j][2];
+                        minMaxRadius = s.maxRadius;
+                        minMult = s.autoscaleMultiplier;
+                    }
+                    if (d[j][1] > db.max || db.max == null) {
+                        db.max = d[j][1];
+                        maxsidx=i;
+                        maxpidx=j;
+                        maxr = d[j][2];
+                        maxMaxRadius = s.maxRadius;
+                        maxMult = s.autoscaleMultiplier;
+                    }
+                }              
+            }
+        }
+        
+        var minRatio = minr/minMaxRadius;
+        var maxRatio = maxr/maxMaxRadius;
+        
+        // need to estimate the effect of the radius on total axis span and adjust axis accordingly.
+        var span = db.max - db.min;
+        // var dim = (this.name == 'xaxis' || this.name == 'x2axis') ? this._plotDimensions.width : this._plotDimensions.height;
+        var dim = Math.min(this._plotDimensions.width, this._plotDimensions.height);
+        
+        var minfact = minRatio * minMult/3 * span;
+        var maxfact = maxRatio * maxMult/3 * span;
+        db.max += maxfact;
+        db.min -= minfact;
+    };
+    
+    function highlight (plot, sidx, pidx) {
+        plot.plugins.bubbleRenderer.highlightLabelCanvas.empty();
+        var s = plot.series[sidx];
+        var canvas = plot.plugins.bubbleRenderer.highlightCanvas;
+        var ctx = canvas._ctx;
+        ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+        s._highlightedPoint = pidx;
+        plot.plugins.bubbleRenderer.highlightedSeriesIndex = sidx;
+        
+        var color = s.highlightColorGenerator.get(pidx);
+        var x = s.gridData[pidx][0],
+            y = s.gridData[pidx][1],
+            r = s.gridData[pidx][2];
+        ctx.save();
+        ctx.fillStyle = color;
+        ctx.strokeStyle = color;
+        ctx.lineWidth = 1;
+        ctx.beginPath();
+        ctx.arc(x, y, r, 0, 2*Math.PI, 0);
+        ctx.closePath();
+        ctx.fill();
+        ctx.restore();        
+        // bring label to front
+        if (s.labels[pidx]) {
+            plot.plugins.bubbleRenderer.highlightLabel = s.labels[pidx].clone();
+            plot.plugins.bubbleRenderer.highlightLabel.appendTo(plot.plugins.bubbleRenderer.highlightLabelCanvas);
+            plot.plugins.bubbleRenderer.highlightLabel.addClass('jqplot-bubble-label-highlight');
+        }
+    }
+    
+    function unhighlight (plot) {
+        var canvas = plot.plugins.bubbleRenderer.highlightCanvas;
+        var sidx = plot.plugins.bubbleRenderer.highlightedSeriesIndex;
+        plot.plugins.bubbleRenderer.highlightLabelCanvas.empty();
+        canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        for (var i=0; i<plot.series.length; i++) {
+            plot.series[i]._highlightedPoint = null;
+        }
+        plot.plugins.bubbleRenderer.highlightedSeriesIndex = null;
+        plot.target.trigger('jqplotDataUnhighlight');
+    }
+    
+ 
+    function handleMove(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var si = neighbor.seriesIndex;
+            var pi = neighbor.pointIndex;
+            var ins = [si, pi, neighbor.data, plot.series[si].gridData[pi][2]];
+            var evt1 = jQuery.Event('jqplotDataMouseOver');
+            evt1.pageX = ev.pageX;
+            evt1.pageY = ev.pageY;
+            plot.target.trigger(evt1, ins);
+            if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.bubbleRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, ins[0], ins[1]);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    } 
+    
+    function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var si = neighbor.seriesIndex;
+            var pi = neighbor.pointIndex;
+            var ins = [si, pi, neighbor.data, plot.series[si].gridData[pi][2]];
+            if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.bubbleRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, ins[0], ins[1]);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    }
+    
+    function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
+        var idx = plot.plugins.bubbleRenderer.highlightedSeriesIndex;
+        if (idx != null && plot.series[idx].highlightMouseDown) {
+            unhighlight(plot);
+        }
+    }
+    
+    function handleClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var si = neighbor.seriesIndex;
+            var pi = neighbor.pointIndex;
+            var ins = [si, pi, neighbor.data, plot.series[si].gridData[pi][2]];
+            var evt = jQuery.Event('jqplotDataClick');
+            evt.which = ev.which;
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            plot.target.trigger(evt, ins);
+        }
+    }
+    
+    function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var si = neighbor.seriesIndex;
+            var pi = neighbor.pointIndex;
+            var ins = [si, pi, neighbor.data, plot.series[si].gridData[pi][2]];
+            var idx = plot.plugins.bubbleRenderer.highlightedSeriesIndex;
+            if (idx != null && plot.series[idx].highlightMouseDown) {
+                unhighlight(plot);
+            }
+            var evt = jQuery.Event('jqplotDataRightClick');
+            evt.which = ev.which;
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            plot.target.trigger(evt, ins);
+        }
+    }
+    
+    // called within context of plot
+    // create a canvas which we can draw on.
+    // insert it before the eventCanvas, so eventCanvas will still capture events.
+    function postPlotDraw() {
+        // Memory Leaks patch    
+        if (this.plugins.bubbleRenderer && this.plugins.bubbleRenderer.highlightCanvas) {
+            this.plugins.bubbleRenderer.highlightCanvas.resetCanvas();
+            this.plugins.bubbleRenderer.highlightCanvas = null;
+        }
+        
+        this.plugins.bubbleRenderer = {highlightedSeriesIndex:null};
+        this.plugins.bubbleRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
+        this.plugins.bubbleRenderer.highlightLabel = null;
+        this.plugins.bubbleRenderer.highlightLabelCanvas = $('<div style="position:absolute;"></div>');
+        var top = this._gridPadding.top;
+        var left = this._gridPadding.left;
+        var width = this._plotDimensions.width - this._gridPadding.left - this._gridPadding.right;
+        var height = this._plotDimensions.height - this._gridPadding.top - this._gridPadding.bottom;
+        this.plugins.bubbleRenderer.highlightLabelCanvas.css({top:top, left:left, width:width+'px', height:height+'px'});
+
+        this.eventCanvas._elem.before(this.plugins.bubbleRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-bubbleRenderer-highlight-canvas', this._plotDimensions, this));
+        this.eventCanvas._elem.before(this.plugins.bubbleRenderer.highlightLabelCanvas);
+        
+        var hctx = this.plugins.bubbleRenderer.highlightCanvas.setContext();
+    }
+
+    
+    // setup default renderers for axes and legend so user doesn't have to
+    // called with scope of plot
+    function preInit(target, data, options) {
+        options = options || {};
+        options.axesDefaults = options.axesDefaults || {};
+        options.seriesDefaults = options.seriesDefaults || {};
+        // only set these if there is a Bubble series
+        var setopts = false;
+        if (options.seriesDefaults.renderer == $.jqplot.BubbleRenderer) {
+            setopts = true;
+        }
+        else if (options.series) {
+            for (var i=0; i < options.series.length; i++) {
+                if (options.series[i].renderer == $.jqplot.BubbleRenderer) {
+                    setopts = true;
+                }
+            }
+        }
+        
+        if (setopts) {
+            options.axesDefaults.renderer = $.jqplot.BubbleAxisRenderer;
+            options.sortData = false;
+        }
+    }
+    
+    $.jqplot.preInitHooks.push(preInit);
+    
+})(jQuery);
+    
+    
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js	(revision 2119)
@@ -0,0 +1,203 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    /**
+    * Class: $.jqplot.CanvasAxisLabelRenderer
+    * Renderer to draw axis labels with a canvas element to support advanced
+    * featrues such as rotated text.  This renderer uses a separate rendering engine
+    * to draw the text on the canvas.  Two modes of rendering the text are available.
+    * If the browser has native font support for canvas fonts (currently Mozila 3.5
+    * and Safari 4), you can enable text rendering with the canvas fillText method.
+    * You do so by setting the "enableFontSupport" option to true. 
+    * 
+    * Browsers lacking native font support will have the text drawn on the canvas
+    * using the Hershey font metrics.  Even if the "enableFontSupport" option is true
+    * non-supporting browsers will still render with the Hershey font.
+    * 
+    */
+    $.jqplot.CanvasAxisLabelRenderer = function(options) {
+        // Group: Properties
+        
+        // prop: angle
+        // angle of text, measured clockwise from x axis.
+        this.angle = 0;
+        // name of the axis associated with this tick
+        this.axis;
+        // prop: show
+        // whether or not to show the tick (mark and label).
+        this.show = true;
+        // prop: showLabel
+        // whether or not to show the label.
+        this.showLabel = true;
+        // prop: label
+        // label for the axis.
+        this.label = '';
+        // prop: fontFamily
+        // CSS spec for the font-family css attribute.
+        // Applies only to browsers supporting native font rendering in the
+        // canvas tag.  Currently Mozilla 3.5 and Safari 4.
+        this.fontFamily = '"Trebuchet MS", Arial, Helvetica, sans-serif';
+        // prop: fontSize
+        // CSS spec for font size.
+        this.fontSize = '11pt';
+        // prop: fontWeight
+        // CSS spec for fontWeight:  normal, bold, bolder, lighter or a number 100 - 900
+        this.fontWeight = 'normal';
+        // prop: fontStretch
+        // Multiplier to condense or expand font width.  
+        // Applies only to browsers which don't support canvas native font rendering.
+        this.fontStretch = 1.0;
+        // prop: textColor
+        // css spec for the color attribute.
+        this.textColor = '#666666';
+        // prop: enableFontSupport
+        // true to turn on native canvas font support in Mozilla 3.5+ and Safari 4+.
+        // If true, label will be drawn with canvas tag native support for fonts.
+        // If false, label will be drawn with Hershey font metrics.
+        this.enableFontSupport = true;
+        // prop: pt2px
+        // Point to pixel scaling factor, used for computing height of bounding box
+        // around a label.  The labels text renderer has a default setting of 1.4, which 
+        // should be suitable for most fonts.  Leave as null to use default.  If tops of
+        // letters appear clipped, increase this.  If bounding box seems too big, decrease.
+        // This is an issue only with the native font renderering capabilities of Mozilla
+        // 3.5 and Safari 4 since they do not provide a method to determine the font height.
+        this.pt2px = null;
+        
+        this._elem;
+        this._ctx;
+        this._plotWidth;
+        this._plotHeight;
+        this._plotDimensions = {height:null, width:null};
+        
+        $.extend(true, this, options);
+        
+        if (options.angle == null && this.axis != 'xaxis' && this.axis != 'x2axis') {
+            this.angle = -90;
+        }
+        
+        var ropts = {fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily};
+        if (this.pt2px) {
+            ropts.pt2px = this.pt2px;
+        }
+        
+        if (this.enableFontSupport) {
+            if ($.jqplot.support_canvas_text()) {
+                this._textRenderer = new $.jqplot.CanvasFontRenderer(ropts);
+            }
+            
+            else {
+                this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts); 
+            }
+        }
+        else {
+            this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts); 
+        }
+    };
+    
+    $.jqplot.CanvasAxisLabelRenderer.prototype.init = function(options) {
+        $.extend(true, this, options);
+        this._textRenderer.init({fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily});
+    };
+    
+    // return width along the x axis
+    // will check first to see if an element exists.
+    // if not, will return the computed text box width.
+    $.jqplot.CanvasAxisLabelRenderer.prototype.getWidth = function(ctx) {
+        if (this._elem) {
+         return this._elem.outerWidth(true);
+        }
+        else {
+            var tr = this._textRenderer;
+            var l = tr.getWidth(ctx);
+            var h = tr.getHeight(ctx);
+            var w = Math.abs(Math.sin(tr.angle)*h) + Math.abs(Math.cos(tr.angle)*l);
+            return w;
+        }
+    };
+    
+    // return height along the y axis.
+    $.jqplot.CanvasAxisLabelRenderer.prototype.getHeight = function(ctx) {
+        if (this._elem) {
+         return this._elem.outerHeight(true);
+        }
+        else {
+            var tr = this._textRenderer;
+            var l = tr.getWidth(ctx);
+            var h = tr.getHeight(ctx);
+            var w = Math.abs(Math.cos(tr.angle)*h) + Math.abs(Math.sin(tr.angle)*l);
+            return w;
+        }
+    };
+    
+    $.jqplot.CanvasAxisLabelRenderer.prototype.getAngleRad = function() {
+        var a = this.angle * Math.PI/180;
+        return a;
+    };
+    
+    $.jqplot.CanvasAxisLabelRenderer.prototype.draw = function(ctx, plot) {
+          // Memory Leaks patch
+          if (this._elem) {
+              if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+                  window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
+              }
+            
+              this._elem.emptyForce();
+              this._elem = null;
+          }
+
+        // create a canvas here, but can't draw on it untill it is appended
+        // to dom for IE compatability.
+        var elem = plot.canvasManager.getCanvas();
+
+        this._textRenderer.setText(this.label, ctx);
+        var w = this.getWidth(ctx);
+        var h = this.getHeight(ctx);
+        elem.width = w;
+        elem.height = h;
+        elem.style.width = w;
+        elem.style.height = h;
+        
+        elem = plot.canvasManager.initCanvas(elem);
+
+        this._elem = $(elem);
+        this._elem.css({ position: 'absolute'});
+        this._elem.addClass('jqplot-'+this.axis+'-label');
+        
+        elem = null;
+        return this._elem;
+    };
+    
+    $.jqplot.CanvasAxisLabelRenderer.prototype.pack = function() {
+        this._textRenderer.draw(this._elem.get(0).getContext("2d"), this.label);
+    };
+    
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.canvasAxisTickRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.canvasAxisTickRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.canvasAxisTickRenderer.js	(revision 2119)
@@ -0,0 +1,253 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    /**
+    *  Class: $.jqplot.CanvasAxisTickRenderer
+    * Renderer to draw axis ticks with a canvas element to support advanced
+    * featrues such as rotated text.  This renderer uses a separate rendering engine
+    * to draw the text on the canvas.  Two modes of rendering the text are available.
+    * If the browser has native font support for canvas fonts (currently Mozila 3.5
+    * and Safari 4), you can enable text rendering with the canvas fillText method.
+    * You do so by setting the "enableFontSupport" option to true. 
+    * 
+    * Browsers lacking native font support will have the text drawn on the canvas
+    * using the Hershey font metrics.  Even if the "enableFontSupport" option is true
+    * non-supporting browsers will still render with the Hershey font.
+    */
+    $.jqplot.CanvasAxisTickRenderer = function(options) {
+        // Group: Properties
+        
+        // prop: mark
+        // tick mark on the axis.  One of 'inside', 'outside', 'cross', '' or null.
+        this.mark = 'outside';
+        // prop: showMark
+        // whether or not to show the mark on the axis.
+        this.showMark = true;
+        // prop: showGridline
+        // whether or not to draw the gridline on the grid at this tick.
+        this.showGridline = true;
+        // prop: isMinorTick
+        // if this is a minor tick.
+        this.isMinorTick = false;
+        // prop: angle
+        // angle of text, measured clockwise from x axis.
+        this.angle = 0;
+        // prop:  markSize
+        // Length of the tick marks in pixels.  For 'cross' style, length
+        // will be stoked above and below axis, so total length will be twice this.
+        this.markSize = 4;
+        // prop: show
+        // whether or not to show the tick (mark and label).
+        this.show = true;
+        // prop: showLabel
+        // whether or not to show the label.
+        this.showLabel = true;
+        // prop: labelPosition
+        // 'auto', 'start', 'middle' or 'end'.
+        // Whether tick label should be positioned so the start, middle, or end
+        // of the tick mark.
+        this.labelPosition = 'auto';
+        this.label = '';
+        this.value = null;
+        this._styles = {};
+        // prop: formatter
+        // A class of a formatter for the tick text.
+        // The default $.jqplot.DefaultTickFormatter uses sprintf.
+        this.formatter = $.jqplot.DefaultTickFormatter;
+        // prop: formatString
+        // string passed to the formatter.
+        this.formatString = '';
+        // prop: prefix
+        // String to prepend to the tick label.
+        // Prefix is prepended to the formatted tick label.
+        this.prefix = '';
+        // prop: fontFamily
+        // css spec for the font-family css attribute.
+        this.fontFamily = '"Trebuchet MS", Arial, Helvetica, sans-serif';
+        // prop: fontSize
+        // CSS spec for font size.
+        this.fontSize = '10pt';
+        // prop: fontWeight
+        // CSS spec for fontWeight
+        this.fontWeight = 'normal';
+        // prop: fontStretch
+        // Multiplier to condense or expand font width.  
+        // Applies only to browsers which don't support canvas native font rendering.
+        this.fontStretch = 1.0;
+        // prop: textColor
+        // css spec for the color attribute.
+        this.textColor = '#666666';
+        // prop: enableFontSupport
+        // true to turn on native canvas font support in Mozilla 3.5+ and Safari 4+.
+        // If true, tick label will be drawn with canvas tag native support for fonts.
+        // If false, tick label will be drawn with Hershey font metrics.
+        this.enableFontSupport = true;
+        // prop: pt2px
+        // Point to pixel scaling factor, used for computing height of bounding box
+        // around a label.  The labels text renderer has a default setting of 1.4, which 
+        // should be suitable for most fonts.  Leave as null to use default.  If tops of
+        // letters appear clipped, increase this.  If bounding box seems too big, decrease.
+        // This is an issue only with the native font renderering capabilities of Mozilla
+        // 3.5 and Safari 4 since they do not provide a method to determine the font height.
+        this.pt2px = null;
+        
+        this._elem;
+        this._ctx;
+        this._plotWidth;
+        this._plotHeight;
+        this._plotDimensions = {height:null, width:null};
+        
+        $.extend(true, this, options);
+        
+        var ropts = {fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily};
+        if (this.pt2px) {
+            ropts.pt2px = this.pt2px;
+        }
+        
+        if (this.enableFontSupport) {
+            if ($.jqplot.support_canvas_text()) {
+                this._textRenderer = new $.jqplot.CanvasFontRenderer(ropts);
+            }
+            
+            else {
+                this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts); 
+            }
+        }
+        else {
+            this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts); 
+        }
+    };
+    
+    $.jqplot.CanvasAxisTickRenderer.prototype.init = function(options) {
+        $.extend(true, this, options);
+        this._textRenderer.init({fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily});
+    };
+    
+    // return width along the x axis
+    // will check first to see if an element exists.
+    // if not, will return the computed text box width.
+    $.jqplot.CanvasAxisTickRenderer.prototype.getWidth = function(ctx) {
+        if (this._elem) {
+         return this._elem.outerWidth(true);
+        }
+        else {
+            var tr = this._textRenderer;
+            var l = tr.getWidth(ctx);
+            var h = tr.getHeight(ctx);
+            var w = Math.abs(Math.sin(tr.angle)*h) + Math.abs(Math.cos(tr.angle)*l);
+            return w;
+        }
+    };
+    
+    // return height along the y axis.
+    $.jqplot.CanvasAxisTickRenderer.prototype.getHeight = function(ctx) {
+        if (this._elem) {
+         return this._elem.outerHeight(true);
+        }
+        else {
+            var tr = this._textRenderer;
+            var l = tr.getWidth(ctx);
+            var h = tr.getHeight(ctx);
+            var w = Math.abs(Math.cos(tr.angle)*h) + Math.abs(Math.sin(tr.angle)*l);
+            return w;
+        }
+    };
+
+    // return top.
+    $.jqplot.CanvasAxisTickRenderer.prototype.getTop = function(ctx) {
+        if (this._elem) {
+         return this._elem.position().top;
+        }
+        else {
+            return null;
+        }
+    };
+    
+    $.jqplot.CanvasAxisTickRenderer.prototype.getAngleRad = function() {
+        var a = this.angle * Math.PI/180;
+        return a;
+    };
+    
+    
+    $.jqplot.CanvasAxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) {
+        this.value = value;
+        if (isMinor) {
+            this.isMinorTick = true;
+        }
+        return this;
+    };
+    
+    $.jqplot.CanvasAxisTickRenderer.prototype.draw = function(ctx, plot) {
+        if (!this.label) {
+            this.label = this.prefix + this.formatter(this.formatString, this.value);
+        }
+        
+        // Memory Leaks patch
+        if (this._elem) {
+            if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
+                window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
+            }
+            
+            this._elem.emptyForce();
+            this._elem = null;
+        }
+
+        // create a canvas here, but can't draw on it untill it is appended
+        // to dom for IE compatability.
+
+        var elem = plot.canvasManager.getCanvas();
+
+        this._textRenderer.setText(this.label, ctx);
+        var w = this.getWidth(ctx);
+        var h = this.getHeight(ctx);
+        // canvases seem to need to have width and heigh attributes directly set.
+        elem.width = w;
+        elem.height = h;
+        elem.style.width = w;
+        elem.style.height = h;
+        elem.style.textAlign = 'left';
+        elem.style.position = 'absolute';
+
+        elem = plot.canvasManager.initCanvas(elem);
+
+        this._elem = $(elem);
+        this._elem.css(this._styles);
+        this._elem.addClass('jqplot-'+this.axis+'-tick');
+
+        elem = null;
+        return this._elem;
+    };
+    
+    $.jqplot.CanvasAxisTickRenderer.prototype.pack = function() {
+        this._textRenderer.draw(this._elem.get(0).getContext("2d"), this.label);
+    };
+    
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.canvasOverlay.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.canvasOverlay.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.canvasOverlay.js	(revision 2119)
@@ -0,0 +1,1021 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    var objCounter = 0;
+    // class: $.jqplot.CanvasOverlay
+    $.jqplot.CanvasOverlay = function(opts){
+        var options = opts || {};
+        this.options = {
+            show: $.jqplot.config.enablePlugins,
+            deferDraw: false
+        };
+        // prop: objects
+        this.objects = [];
+        this.objectNames = [];
+        this.canvas = null;
+        this.markerRenderer = new $.jqplot.MarkerRenderer({style:'line'});
+        this.markerRenderer.init();
+        this.highlightObjectIndex = null;
+        if (options.objects) {
+            var objs = options.objects,
+                obj;
+            for (var i=0; i<objs.length; i++) {
+                obj = objs[i];
+                for (var n in obj) {
+                    switch (n) {
+                        case 'line':
+                            this.addLine(obj[n]);
+                            break;
+                        case 'horizontalLine':
+                            this.addHorizontalLine(obj[n]);
+                            break;
+                        case 'dashedHorizontalLine':
+                            this.addDashedHorizontalLine(obj[n]);
+                            break;
+                        case 'verticalLine':
+                            this.addVerticalLine(obj[n]);
+                            break;
+                        case 'dashedVerticalLine':
+                            this.addDashedVerticalLine(obj[n]);
+                            break;
+                        case 'rectangle':
+                            this.addRectangle(obj[n]);
+                            break;
+                        default:
+                            break;
+                    }
+                }   
+            }
+        }
+        $.extend(true, this.options, options);
+    };
+    
+    // called with scope of a plot object
+    $.jqplot.CanvasOverlay.postPlotInit = function (target, data, opts) {
+        var options = opts || {};
+        // add a canvasOverlay attribute to the plot
+        this.plugins.canvasOverlay = new $.jqplot.CanvasOverlay(options.canvasOverlay);     
+    };
+
+
+    function LineBase() {
+        this.uid = null;
+        this.type = null;
+        this.gridStart = null;
+        this.gridStop = null;
+        this.tooltipWidthFactor = 0;
+        this.options = {           
+            // prop: name
+            // Optional name for the overlay object.
+            // Can be later used to retrieve the object by name.
+            name: null,
+            // prop: show
+            // true to show (draw), false to not draw.
+            show: true,
+            // prop: lineWidth
+            // Width of the line.
+            lineWidth: 2,
+            // prop: lineCap
+            // Type of ending placed on the line ['round', 'butt', 'square']
+            lineCap: 'round',
+            // prop: color
+            // color of the line
+            color: '#666666',
+            // prop: shadow
+            // whether or not to draw a shadow on the line
+            shadow: true,
+            // prop: shadowAngle
+            // Shadow angle in degrees
+            shadowAngle: 45,
+            // prop: shadowOffset
+            // Shadow offset from line in pixels
+            shadowOffset: 1,
+            // prop: shadowDepth
+            // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
+            shadowDepth: 3,
+            // prop: shadowAlpha
+            // Alpha channel transparency of shadow.  0 = transparent.
+            shadowAlpha: '0.07',
+            // prop: xaxis
+            // X axis to use for positioning/scaling the line.
+            xaxis: 'xaxis',
+            // prop: yaxis
+            // Y axis to use for positioning/scaling the line.
+            yaxis: 'yaxis',
+            // prop: showTooltip
+            // Show a tooltip with data point values.
+            showTooltip: false,
+            // prop: showTooltipPrecision
+            // Controls how close to line cursor must be to show tooltip.
+            // Higher number = closer to line, lower number = farther from line.
+            // 1.0 = cursor must be over line.
+            showTooltipPrecision: 0.6,
+            // prop: tooltipLocation
+            // Where to position tooltip, 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
+            tooltipLocation: 'nw',
+            // prop: fadeTooltip
+            // true = fade in/out tooltip, flase = show/hide tooltip
+            fadeTooltip: true,
+            // prop: tooltipFadeSpeed
+            // 'slow', 'def', 'fast', or number of milliseconds.
+            tooltipFadeSpeed: "fast",
+            // prop: tooltipOffset
+            // Pixel offset of tooltip from the highlight.
+            tooltipOffset: 4,
+            // prop: tooltipFormatString
+            // Format string passed the x and y values of the cursor on the line.
+            // e.g., 'Dogs: %.2f, Cats: %d'.
+            tooltipFormatString: '%d, %d'
+        };
+    }
+    
+    
+    function Rectangle(options) {
+        LineBase.call(this);
+        this.type = 'rectangle';
+        var opts = {
+         // prop: xmin
+                // x value for the start of the line, null to scale to axis min.
+                xmin: null,
+                // prop: xmax
+                // x value for the end of the line, null to scale to axis max.
+                xmax: null,
+                // prop xOffset
+                // offset ends of the line inside the grid. Number
+                xOffset: '6px', // number or string. Number interpreted as units, string as pixels.
+                xminOffset: null,
+                xmaxOffset: null,
+                
+                ymin: null,
+                ymax: null,
+                yOffset: '6px', // number or string. Number interpreted as units, string as pixels.
+                yminOffset: null,
+                ymaxOffset: null
+        };
+        $.extend(true, this.options, opts, options);
+
+        if (this.options.showTooltipPrecision < 0.01) {
+            this.options.showTooltipPrecision = 0.01;
+        }
+    }
+
+    Rectangle.prototype = new LineBase();
+    Rectangle.prototype.constructor = Rectangle;
+
+    
+    /**
+     * Class: Line
+     * A straight line.
+     */
+    function Line(options) {
+        LineBase.call(this);
+        this.type = 'line';
+        var opts = {
+            // prop: start
+            // [x, y] coordinates for the start of the line.
+            start: [],
+            // prop: stop
+            // [x, y] coordinates for the end of the line.
+            stop: []
+        };
+        $.extend(true, this.options, opts, options);
+
+        if (this.options.showTooltipPrecision < 0.01) {
+            this.options.showTooltipPrecision = 0.01;
+        }
+    }
+
+    Line.prototype = new LineBase();
+    Line.prototype.constructor = Line;
+
+
+    /**
+     * Class: HorizontalLine
+     * A straight horizontal line.
+     */
+    function HorizontalLine(options) {
+        LineBase.call(this);
+        this.type = 'horizontalLine';
+        var opts = {
+            // prop: y
+            // y value to position the line
+            y: null,
+            // prop: xmin
+            // x value for the start of the line, null to scale to axis min.
+            xmin: null,
+            // prop: xmax
+            // x value for the end of the line, null to scale to axis max.
+            xmax: null,
+            // prop xOffset
+            // offset ends of the line inside the grid.  Number 
+            xOffset: '6px', // number or string.  Number interpreted as units, string as pixels.
+            xminOffset: null,
+            xmaxOffset: null
+        };
+        $.extend(true, this.options, opts, options);
+
+        if (this.options.showTooltipPrecision < 0.01) {
+            this.options.showTooltipPrecision = 0.01;
+        }
+    }
+
+    HorizontalLine.prototype = new LineBase();
+    HorizontalLine.prototype.constructor = HorizontalLine;
+    
+
+    /**
+     * Class: DashedHorizontalLine
+     * A straight dashed horizontal line.
+     */
+    function DashedHorizontalLine(options) {
+        LineBase.call(this);
+        this.type = 'dashedHorizontalLine';
+        var opts = {
+            y: null,
+            xmin: null,
+            xmax: null,
+            xOffset: '6px', // number or string.  Number interpreted as units, string as pixels.
+            xminOffset: null,
+            xmaxOffset: null,
+            // prop: dashPattern
+            // Array of line, space settings in pixels.
+            // Default is 8 pixel of line, 8 pixel of space.
+            // Note, limit to a 2 element array b/c of bug with higher order arrays.
+            dashPattern: [8,8]
+        };
+        $.extend(true, this.options, opts, options);
+
+        if (this.options.showTooltipPrecision < 0.01) {
+            this.options.showTooltipPrecision = 0.01;
+        }
+    }
+
+    DashedHorizontalLine.prototype = new LineBase();
+    DashedHorizontalLine.prototype.constructor = DashedHorizontalLine;
+    
+
+    /**
+     * Class: VerticalLine
+     * A straight vertical line.
+     */
+    function VerticalLine(options) {
+        LineBase.call(this);
+        this.type = 'verticalLine';
+        var opts = {
+            x: null,
+            ymin: null,
+            ymax: null,
+            yOffset: '6px', // number or string.  Number interpreted as units, string as pixels.
+            yminOffset: null,
+            ymaxOffset: null
+        };
+        $.extend(true, this.options, opts, options);
+
+        if (this.options.showTooltipPrecision < 0.01) {
+            this.options.showTooltipPrecision = 0.01;
+        }
+    }
+
+    VerticalLine.prototype = new LineBase();
+    VerticalLine.prototype.constructor = VerticalLine;
+    
+
+    /**
+     * Class: DashedVerticalLine
+     * A straight dashed vertical line.
+     */
+    function DashedVerticalLine(options) {
+        LineBase.call(this);
+        this.type = 'dashedVerticalLine';
+        this.start = null;
+        this.stop = null;
+        var opts = {
+            x: null,
+            ymin: null,
+            ymax: null,
+            yOffset: '6px', // number or string.  Number interpreted as units, string as pixels.
+            yminOffset: null,
+            ymaxOffset: null,
+            // prop: dashPattern
+            // Array of line, space settings in pixels.
+            // Default is 8 pixel of line, 8 pixel of space.
+            // Note, limit to a 2 element array b/c of bug with higher order arrays.
+            dashPattern: [8,8]
+        };
+        $.extend(true, this.options, opts, options);
+
+        if (this.options.showTooltipPrecision < 0.01) {
+            this.options.showTooltipPrecision = 0.01;
+        }
+    }
+
+    DashedVerticalLine.prototype = new LineBase();
+    DashedVerticalLine.prototype.constructor = DashedVerticalLine;
+    
+    $.jqplot.CanvasOverlay.prototype.addLine = function(opts) {
+        var line = new Line(opts);
+        line.uid = objCounter++;
+        this.objects.push(line);
+        this.objectNames.push(line.options.name);
+    };
+    
+    $.jqplot.CanvasOverlay.prototype.addHorizontalLine = function(opts) {
+        var line = new HorizontalLine(opts);
+        line.uid = objCounter++;
+        this.objects.push(line);
+        this.objectNames.push(line.options.name);
+    };
+    
+    $.jqplot.CanvasOverlay.prototype.addDashedHorizontalLine = function(opts) {
+        var line = new DashedHorizontalLine(opts);
+        line.uid = objCounter++;
+        this.objects.push(line);
+        this.objectNames.push(line.options.name);
+    };
+    
+    $.jqplot.CanvasOverlay.prototype.addVerticalLine = function(opts) {
+        var line = new VerticalLine(opts);
+        line.uid = objCounter++;
+        this.objects.push(line);
+        this.objectNames.push(line.options.name);
+    };
+    
+    $.jqplot.CanvasOverlay.prototype.addDashedVerticalLine = function(opts) {
+        var line = new DashedVerticalLine(opts);
+        line.uid = objCounter++;
+        this.objects.push(line);
+        this.objectNames.push(line.options.name);
+    };
+    
+    $.jqplot.CanvasOverlay.prototype.addRectangle = function(opts) {
+        var line = new Rectangle(opts);
+        line.uid = objCounter++;
+        this.objects.push(line);
+        this.objectNames.push(line.options.name);
+    };
+    
+    $.jqplot.CanvasOverlay.prototype.removeObject = function(idx) {
+        // check if integer, remove by index
+        if ($.type(idx) == 'number') {
+            this.objects.splice(idx, 1);
+            this.objectNames.splice(idx, 1);
+        }
+        // if string, remove by name
+        else {
+            var id = $.inArray(idx, this.objectNames);
+            if (id != -1) {
+                this.objects.splice(id, 1);
+                this.objectNames.splice(id, 1);
+            }
+        }
+    };
+    
+    $.jqplot.CanvasOverlay.prototype.getObject = function(idx) {
+        // check if integer, remove by index
+        if ($.type(idx) == 'number') {
+            return this.objects[idx];
+        }
+        // if string, remove by name
+        else {
+            var id = $.inArray(idx, this.objectNames);
+            if (id != -1) {
+                return this.objects[id];
+            }
+        }
+    };
+    
+    // Set get as alias for getObject.
+    $.jqplot.CanvasOverlay.prototype.get = $.jqplot.CanvasOverlay.prototype.getObject;
+    
+    $.jqplot.CanvasOverlay.prototype.clear = function(plot) {
+        this.canvas._ctx.clearRect(0,0,this.canvas.getWidth(), this.canvas.getHeight());
+    };
+    
+    $.jqplot.CanvasOverlay.prototype.draw = function(plot) {
+        var obj, 
+            objs = this.objects,
+            mr = this.markerRenderer,
+            start,
+            stop;
+        if (this.options.show) {
+            this.canvas._ctx.clearRect(0,0,this.canvas.getWidth(), this.canvas.getHeight());
+            for (var k=0; k<objs.length; k++) {
+                obj = objs[k];
+                var opts = $.extend(true, {}, obj.options);
+                if (obj.options.show) {
+                    // style and shadow properties should be set before
+                    // every draw of marker renderer.
+                    mr.shadow = obj.options.shadow;
+                    obj.tooltipWidthFactor = obj.options.lineWidth / obj.options.showTooltipPrecision;
+                    switch (obj.type) {
+                        case 'line':
+                            // style and shadow properties should be set before
+                            // every draw of marker renderer.
+                            mr.style = 'line';
+                            opts.closePath = false;
+                            start = [plot.axes[obj.options.xaxis].series_u2p(obj.options.start[0]), plot.axes[obj.options.yaxis].series_u2p(obj.options.start[1])];
+                            stop = [plot.axes[obj.options.xaxis].series_u2p(obj.options.stop[0]), plot.axes[obj.options.yaxis].series_u2p(obj.options.stop[1])];
+                            obj.gridStart = start;
+                            obj.gridStop = stop;
+                            mr.draw(start, stop, this.canvas._ctx, opts);
+                            break;
+                        case 'horizontalLine':
+                            
+                            // style and shadow properties should be set before
+                            // every draw of marker renderer.
+                            if (obj.options.y != null) {
+                                mr.style = 'line';
+                                opts.closePath = false;
+                                var xaxis = plot.axes[obj.options.xaxis],
+                                    xstart,
+                                    xstop,
+                                    y = plot.axes[obj.options.yaxis].series_u2p(obj.options.y),
+                                    xminoff = obj.options.xminOffset || obj.options.xOffset,
+                                    xmaxoff = obj.options.xmaxOffset || obj.options.xOffset;
+                                if (obj.options.xmin != null) {
+                                    xstart = xaxis.series_u2p(obj.options.xmin);
+                                }
+                                else if (xminoff != null) {
+                                    if ($.type(xminoff) == "number") {
+                                        xstart = xaxis.series_u2p(xaxis.min + xminoff);
+                                    }
+                                    else if ($.type(xminoff) == "string") {
+                                        xstart = xaxis.series_u2p(xaxis.min) + parseFloat(xminoff);
+                                    }
+                                }
+                                if (obj.options.xmax != null) {
+                                    xstop = xaxis.series_u2p(obj.options.xmax);
+                                }
+                                else if (xmaxoff != null) {
+                                    if ($.type(xmaxoff) == "number") {
+                                        xstop = xaxis.series_u2p(xaxis.max - xmaxoff);
+                                    }
+                                    else if ($.type(xmaxoff) == "string") {
+                                        xstop = xaxis.series_u2p(xaxis.max) - parseFloat(xmaxoff);
+                                    }
+                                }
+                                if (xstop != null && xstart != null) {
+                                    obj.gridStart = [xstart, y];
+                                    obj.gridStop = [xstop, y];
+                                    mr.draw([xstart, y], [xstop, y], this.canvas._ctx, opts);
+                                }
+                            }
+                            break;
+
+                        case 'dashedHorizontalLine':
+                            
+                            var dashPat = obj.options.dashPattern;
+                            var dashPatLen = 0;
+                            for (var i=0; i<dashPat.length; i++) {
+                                dashPatLen += dashPat[i];
+                            }
+
+                            // style and shadow properties should be set before
+                            // every draw of marker renderer.
+                            if (obj.options.y != null) {
+                                mr.style = 'line';
+                                opts.closePath = false;
+                                var xaxis = plot.axes[obj.options.xaxis],
+                                    xstart,
+                                    xstop,
+                                    y = plot.axes[obj.options.yaxis].series_u2p(obj.options.y),
+                                    xminoff = obj.options.xminOffset || obj.options.xOffset,
+                                    xmaxoff = obj.options.xmaxOffset || obj.options.xOffset;
+                                if (obj.options.xmin != null) {
+                                    xstart = xaxis.series_u2p(obj.options.xmin);
+                                }
+                                else if (xminoff != null) {
+                                    if ($.type(xminoff) == "number") {
+                                        xstart = xaxis.series_u2p(xaxis.min + xminoff);
+                                    }
+                                    else if ($.type(xminoff) == "string") {
+                                        xstart = xaxis.series_u2p(xaxis.min) + parseFloat(xminoff);
+                                    }
+                                }
+                                if (obj.options.xmax != null) {
+                                    xstop = xaxis.series_u2p(obj.options.xmax);
+                                }
+                                else if (xmaxoff != null) {
+                                    if ($.type(xmaxoff) == "number") {
+                                        xstop = xaxis.series_u2p(xaxis.max - xmaxoff);
+                                    }
+                                    else if ($.type(xmaxoff) == "string") {
+                                        xstop = xaxis.series_u2p(xaxis.max) - parseFloat(xmaxoff);
+                                    }
+                                }
+                                if (xstop != null && xstart != null) {
+                                    obj.gridStart = [xstart, y];
+                                    obj.gridStop = [xstop, y];
+                                    var numDash = Math.ceil((xstop - xstart)/dashPatLen);
+                                    var b=xstart, e;
+                                    for (var i=0; i<numDash; i++) {
+                                        for (var j=0; j<dashPat.length; j+=2) {
+                                            e = b+dashPat[j];
+                                            mr.draw([b, y], [e, y], this.canvas._ctx, opts);
+                                            b += dashPat[j];
+                                            if (j < dashPat.length-1) {
+                                                b += dashPat[j+1];
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                            break;
+
+                        case 'verticalLine':
+                            
+                            // style and shadow properties should be set before
+                            // every draw of marker renderer.
+                            if (obj.options.x != null) {
+                                mr.style = 'line';
+                                opts.closePath = false;
+                                var yaxis = plot.axes[obj.options.yaxis],
+                                    ystart,
+                                    ystop,
+                                    x = plot.axes[obj.options.xaxis].series_u2p(obj.options.x),
+                                    yminoff = obj.options.yminOffset || obj.options.yOffset,
+                                    ymaxoff = obj.options.ymaxOffset || obj.options.yOffset;
+                                if (obj.options.ymin != null) {
+                                    ystart = yaxis.series_u2p(obj.options.ymin);
+                                }
+                                else if (yminoff != null) {
+                                    if ($.type(yminoff) == "number") {
+                                        ystart = yaxis.series_u2p(yaxis.min - yminoff);
+                                    }
+                                    else if ($.type(yminoff) == "string") {
+                                        ystart = yaxis.series_u2p(yaxis.min) - parseFloat(yminoff);
+                                    }
+                                }
+                                if (obj.options.ymax != null) {
+                                    ystop = yaxis.series_u2p(obj.options.ymax);
+                                }
+                                else if (ymaxoff != null) {
+                                    if ($.type(ymaxoff) == "number") {
+                                        ystop = yaxis.series_u2p(yaxis.max + ymaxoff);
+                                    }
+                                    else if ($.type(ymaxoff) == "string") {
+                                        ystop = yaxis.series_u2p(yaxis.max) + parseFloat(ymaxoff);
+                                    }
+                                }
+                                if (ystop != null && ystart != null) {
+                                    obj.gridStart = [x, ystart];
+                                    obj.gridStop = [x, ystop];
+                                    mr.draw([x, ystart], [x, ystop], this.canvas._ctx, opts);
+                                }
+                            }
+                            break;
+
+                        case 'dashedVerticalLine':
+                            
+                            var dashPat = obj.options.dashPattern;
+                            var dashPatLen = 0;
+                            for (var i=0; i<dashPat.length; i++) {
+                                dashPatLen += dashPat[i];
+                            }
+
+                            // style and shadow properties should be set before
+                            // every draw of marker renderer.
+                            if (obj.options.x != null) {
+                                mr.style = 'line';
+                                opts.closePath = false;
+                                var yaxis = plot.axes[obj.options.yaxis],
+                                    ystart,
+                                    ystop,
+                                    x = plot.axes[obj.options.xaxis].series_u2p(obj.options.x),
+                                    yminoff = obj.options.yminOffset || obj.options.yOffset,
+                                    ymaxoff = obj.options.ymaxOffset || obj.options.yOffset;
+                                if (obj.options.ymin != null) {
+                                    ystart = yaxis.series_u2p(obj.options.ymin);
+                                }
+                                else if (yminoff != null) {
+                                    if ($.type(yminoff) == "number") {
+                                        ystart = yaxis.series_u2p(yaxis.min - yminoff);
+                                    }
+                                    else if ($.type(yminoff) == "string") {
+                                        ystart = yaxis.series_u2p(yaxis.min) - parseFloat(yminoff);
+                                    }
+                                }
+                                if (obj.options.ymax != null) {
+                                    ystop = yaxis.series_u2p(obj.options.ymax);
+                                }
+                                else if (ymaxoff != null) {
+                                    if ($.type(ymaxoff) == "number") {
+                                        ystop = yaxis.series_u2p(yaxis.max + ymaxoff);
+                                    }
+                                    else if ($.type(ymaxoff) == "string") {
+                                        ystop = yaxis.series_u2p(yaxis.max) + parseFloat(ymaxoff);
+                                    }
+                                }
+
+
+                                if (ystop != null && ystart != null) {
+                                    obj.gridStart = [x, ystart];
+                                    obj.gridStop = [x, ystop];
+                                    var numDash = Math.ceil((ystart - ystop)/dashPatLen);
+                                    var firstDashAdjust = ((numDash * dashPatLen) - (ystart - ystop))/2.0;
+                                    var b=ystart, e, bs, es;
+                                    for (var i=0; i<numDash; i++) {
+                                        for (var j=0; j<dashPat.length; j+=2) {
+                                            e = b - dashPat[j];
+                                            if (e < ystop) {
+                                                e = ystop;
+                                            }
+                                            if (b < ystop) {
+                                                b = ystop;
+                                            }
+                                            // es = e;
+                                            // if (i == 0) {
+                                            //  es += firstDashAdjust;
+                                            // }
+                                            mr.draw([x, b], [x, e], this.canvas._ctx, opts);
+                                            b -= dashPat[j];
+                                            if (j < dashPat.length-1) {
+                                                b -= dashPat[j+1];
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                            break;
+                            
+                        case 'rectangle':
+                            // style and shadow properties should be set before
+                            // every draw of marker renderer.
+                            mr.style = 'line';
+                            opts.closePath = true;
+                            
+                            var xaxis = plot.axes[obj.options.xaxis],
+                                    xstart,
+                                    xstop,
+                                    y = plot.axes[obj.options.yaxis].series_u2p(obj.options.y),
+                                    xminoff = obj.options.xminOffset || obj.options.xOffset,
+                                    xmaxoff = obj.options.xmaxOffset || obj.options.xOffset;
+                            if (obj.options.xmin != null) {
+                                xstart = xaxis.series_u2p(obj.options.xmin);
+                            }
+                            else if (xminoff != null) {
+                                if ($.type(xminoff) == "number") {
+                                    xstart = xaxis.series_u2p(xaxis.min + xminoff);
+                                }
+                                else if ($.type(xminoff) == "string") {
+                                    xstart = xaxis.series_u2p(xaxis.min) + parseFloat(xminoff);
+                                }
+                            }
+                            if (obj.options.xmax != null) {
+                                xstop = xaxis.series_u2p(obj.options.xmax);
+                            }
+                            else if (xmaxoff != null) {
+                                if ($.type(xmaxoff) == "number") {
+                                    xstop = xaxis.series_u2p(xaxis.max - xmaxoff);
+                                }
+                                else if ($.type(xmaxoff) == "string") {
+                                    xstop = xaxis.series_u2p(xaxis.max) - parseFloat(xmaxoff);
+                                }
+                            }
+                            
+                            var yaxis = plot.axes[obj.options.yaxis],
+                                ystart,
+                                ystop,
+                                x = plot.axes[obj.options.xaxis].series_u2p(obj.options.x),
+                                yminoff = obj.options.yminOffset || obj.options.yOffset,
+                                ymaxoff = obj.options.ymaxOffset || obj.options.yOffset;
+                            if (obj.options.ymin != null) {
+                                ystart = yaxis.series_u2p(obj.options.ymin);
+                            }
+                            else if (yminoff != null) {
+                                if ($.type(yminoff) == "number") {
+                                    ystart = yaxis.series_u2p(yaxis.min - yminoff);
+                                }
+                                else if ($.type(yminoff) == "string") {
+                                    ystart = yaxis.series_u2p(yaxis.min) - parseFloat(yminoff);
+                                }
+                            }
+                            if (obj.options.ymax != null) {
+                                ystop = yaxis.series_u2p(obj.options.ymax);
+                            }
+                            else if (ymaxoff != null) {
+                                if ($.type(ymaxoff) == "number") {
+                                    ystop = yaxis.series_u2p(yaxis.max + ymaxoff);
+                                }
+                                else if ($.type(ymaxoff) == "string") {
+                                    ystop = yaxis.series_u2p(yaxis.max) + parseFloat(ymaxoff);
+                                }
+                            }
+                            
+
+                            if (xstop != null && xstart != null && ystop != null && ystart != null) {
+                                obj.gridStart = [xstart, ystart];
+                                obj.gridStop = [xstop, ystop];
+                                
+                                this.canvas._ctx.fillStyle = obj.options.color;
+                                this.canvas._ctx.fillRect(xstart, ystart, xstop - xstart, ystop - ystart);
+                            }
+                            break;
+
+                        default:
+                            break;
+                    }
+                }
+            }
+        }
+    };
+    
+    // called within context of plot
+    // create a canvas which we can draw on.
+    // insert it before the eventCanvas, so eventCanvas will still capture events.
+    $.jqplot.CanvasOverlay.postPlotDraw = function() {
+        var co = this.plugins.canvasOverlay;
+        // Memory Leaks patch    
+        if (co && co.highlightCanvas) {
+            co.highlightCanvas.resetCanvas();
+            co.highlightCanvas = null;
+        }
+        co.canvas = new $.jqplot.GenericCanvas();
+        
+        this.eventCanvas._elem.before(co.canvas.createElement(this._gridPadding, 'jqplot-overlayCanvas-canvas', this._plotDimensions, this));
+        co.canvas.setContext();
+        if (!co.deferDraw) {
+            co.draw(this);
+        }
+
+        var elem = document.createElement('div');
+        co._tooltipElem = $(elem);
+        elem = null;
+        co._tooltipElem.addClass('jqplot-canvasOverlay-tooltip');
+        co._tooltipElem.css({position:'absolute', display:'none'});
+        
+        this.eventCanvas._elem.before(co._tooltipElem);
+        this.eventCanvas._elem.bind('mouseleave', { elem: co._tooltipElem }, function (ev) { ev.data.elem.hide(); });
+
+        var co = null;
+    };
+
+
+    function showTooltip(plot, obj, gridpos, datapos) {
+        var co = plot.plugins.canvasOverlay;
+        var elem = co._tooltipElem;
+
+        var opts = obj.options, x, y;
+
+        elem.html($.jqplot.sprintf(opts.tooltipFormatString, datapos[0], datapos[1]));
+        
+        switch (opts.tooltipLocation) {
+            case 'nw':
+                x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset;
+                y = gridpos[1] + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true);
+                break;
+            case 'n':
+                x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true)/2;
+                y = gridpos[1] + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true);
+                break;
+            case 'ne':
+                x = gridpos[0] + plot._gridPadding.left + opts.tooltipOffset;
+                y = gridpos[1] + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true);
+                break;
+            case 'e':
+                x = gridpos[0] + plot._gridPadding.left + opts.tooltipOffset;
+                y = gridpos[1] + plot._gridPadding.top - elem.outerHeight(true)/2;
+                break;
+            case 'se':
+                x = gridpos[0] + plot._gridPadding.left + opts.tooltipOffset;
+                y = gridpos[1] + plot._gridPadding.top + opts.tooltipOffset;
+                break;
+            case 's':
+                x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true)/2;
+                y = gridpos[1] + plot._gridPadding.top + opts.tooltipOffset;
+                break;
+            case 'sw':
+                x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset;
+                y = gridpos[1] + plot._gridPadding.top + opts.tooltipOffset;
+                break;
+            case 'w':
+                x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset;
+                y = gridpos[1] + plot._gridPadding.top - elem.outerHeight(true)/2;
+                break;
+            default: // same as 'nw'
+                x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset;
+                y = gridpos[1] + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true);
+                break;
+        }
+
+        elem.css('left', x);
+        elem.css('top', y);
+        if (opts.fadeTooltip) {
+            // Fix for stacked up animations.  Thnanks Trevor!
+            elem.stop(true,true).fadeIn(opts.tooltipFadeSpeed);
+        }
+        else {
+            elem.show();
+        }
+        elem = null;
+    }
+
+
+    function isNearLine(point, lstart, lstop, width) {
+        // r is point to test, p and q are end points.
+        var rx = point[0];
+        var ry = point[1];
+        var px = Math.round(lstop[0]);
+        var py = Math.round(lstop[1]);
+        var qx = Math.round(lstart[0]);
+        var qy = Math.round(lstart[1]);
+
+        var l = Math.sqrt(Math.pow(px-qx, 2) + Math.pow(py-qy, 2));
+
+        // scale error term by length of line.
+        var eps = width*l;
+        var res = Math.abs((qx-px) * (ry-py) - (qy-py) * (rx-px));
+        var ret = (res < eps) ? true : false;
+        return ret;
+    }
+    
+    function isNearRectangle(point, lstart, lstop, width) {
+        // r is point to test, p and q are end points.
+        var rx = point[0];
+        var ry = point[1];
+        var px = Math.round(lstop[0]);
+        var py = Math.round(lstop[1]);
+        var qx = Math.round(lstart[0]);
+        var qy = Math.round(lstart[1]);
+        
+        var temp;
+        if (px > qx) { temp = px; px = qx; qx = temp; }
+        if (py > qy) { temp = py; py = qy; qy = temp; }
+        
+        var ret = (rx >= px && rx <= qx && ry >= py && ry <= qy);
+        
+        return ret;
+    }
+
+
+    function handleMove(ev, gridpos, datapos, neighbor, plot) {
+        var co = plot.plugins.canvasOverlay;
+        var objs = co.objects;
+        var l = objs.length;
+        var obj, haveHighlight=false;
+        var elem;
+        for (var i=0; i<l; i++) {
+            obj = objs[i];
+            if (obj.options.showTooltip) {
+            	var n;
+                if (obj.type === 'rectangle') {
+                 n = isNearRectangle([gridpos.x, gridpos.y], obj.gridStart, obj.gridStop, obj.tooltipWidthFactor);
+                } else {
+                 n = isNearLine([gridpos.x, gridpos.y], obj.gridStart, obj.gridStop, obj.tooltipWidthFactor);
+                }
+                datapos = [plot.axes[obj.options.xaxis].series_p2u(gridpos.x), plot.axes[obj.options.yaxis].series_p2u(gridpos.y)];
+
+                // cases:
+                //    near line, no highlighting
+                //    near line, highliting on this line
+                //    near line, highlighting another line
+                //    not near any line, highlighting
+                //    not near any line, no highlighting
+
+                // near line, not currently highlighting
+                if (n && co.highlightObjectIndex == null) {
+                    switch (obj.type) {
+                        case 'line':
+                            showTooltip(plot, obj, [gridpos.x, gridpos.y], datapos);
+                            break;
+
+                        case 'horizontalLine':
+                        case 'dashedHorizontalLine':
+                            showTooltip(plot, obj, [gridpos.x, obj.gridStart[1]], [datapos[0], obj.options.y]);
+                            break;
+
+                        case 'verticalLine':
+                        case 'dashedVerticalLine':
+                            showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
+                            break;
+                            
+                        case 'rectangle':
+                            showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
+                            break;
+                            
+                        default:
+                            break;
+                    } 
+                    co.highlightObjectIndex = i;
+                    haveHighlight = true;
+                    break;
+                }
+
+                // near line, highlighting another line.
+                else if (n && co.highlightObjectIndex !== i) {
+                    // turn off tooltip.
+                    elem = co._tooltipElem;
+                    if (obj.fadeTooltip) {
+                        elem.fadeOut(obj.tooltipFadeSpeed);
+                    }
+                    else {
+                        elem.hide();
+                    }
+
+                    // turn on right tooltip.
+                    switch (obj.type) {
+                        case 'line':
+                            showTooltip(plot, obj, [gridpos.x, gridpos.y], datapos);
+                            break;
+
+                        case 'horizontalLine':
+                        case 'dashedHorizontalLine':
+                            showTooltip(plot, obj, [gridpos.x, obj.gridStart[1]], [datapos[0], obj.options.y]);
+                            break;
+
+                        case 'verticalLine':
+                        case 'dashedVerticalLine':
+                            showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
+                            break;
+                            
+                        case 'rectangle':
+                            showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
+                            break;
+                            
+                        default:
+                            break;
+                    }
+
+                    co.highlightObjectIndex = i;
+                    haveHighlight = true;
+                    break;
+                }
+
+                // near line, already highlighting this line, update
+                else if (n) {
+                    switch (obj.type) {
+                        case 'line':
+                            showTooltip(plot, obj, [gridpos.x, gridpos.y], datapos);
+                            break;
+
+                        case 'horizontalLine':
+                        case 'dashedHorizontalLine':
+                            showTooltip(plot, obj, [gridpos.x, obj.gridStart[1]], [datapos[0], obj.options.y]);
+                            break;
+
+                        case 'verticalLine':
+                        case 'dashedVerticalLine':
+                            showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
+                            break;
+                            
+                        case 'rectangle':
+                            showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
+                            break;
+                            
+                        default:
+                            break;
+                    }
+
+                    haveHighlight = true;
+                    break;
+                }
+            }
+        }
+
+        // check if we are highlighting and not near a line, turn it off.
+        if (!haveHighlight && co.highlightObjectIndex !== null) {
+            elem = co._tooltipElem;
+            obj = co.getObject(co.highlightObjectIndex);
+            if (obj.fadeTooltip) {
+                elem.fadeOut(obj.tooltipFadeSpeed);
+            }
+            else {
+                elem.hide();
+            }
+            co.highlightObjectIndex = null;
+        }
+    }
+    
+    $.jqplot.postInitHooks.push($.jqplot.CanvasOverlay.postPlotInit);
+    $.jqplot.postDrawHooks.push($.jqplot.CanvasOverlay.postPlotDraw);
+    $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]);
+
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.canvasTextRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.canvasTextRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.canvasTextRenderer.js	(revision 2119)
@@ -0,0 +1,449 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ *
+ * included jsDate library by Chris Leonello:
+ *
+ * Copyright (c) 2010-2013 Chris Leonello
+ *
+ * jsDate is currently available for use in all personal or commercial projects 
+ * under both the MIT and GPL version 2.0 licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * jsDate borrows many concepts and ideas from the Date Instance 
+ * Methods by Ken Snyder along with some parts of Ken's actual code.
+ * 
+ * Ken's original Date Instance Methods and copyright notice:
+ * 
+ * Ken Snyder (ken d snyder at gmail dot com)
+ * 2008-09-10
+ * version 2.0.2 (http://kendsnyder.com/sandbox/date/)     
+ * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
+ *
+ * jqplotToImage function based on Larry Siden's export-jqplot-to-png.js.
+ * Larry has generously given permission to adapt his code for inclusion
+ * into jqPlot.
+ *
+ * Larry's original code can be found here:
+ *
+ * https://github.com/lsiden/export-jqplot-to-png
+ * 
+ * 
+ */
+
+(function($) {    
+    // This code is a modified version of the canvastext.js code, copyright below:
+    //
+    // This code is released to the public domain by Jim Studt, 2007.
+    // He may keep some sort of up to date copy at http://www.federated.com/~jim/canvastext/
+    //
+    $.jqplot.CanvasTextRenderer = function(options){
+        this.fontStyle = 'normal';  // normal, italic, oblique [not implemented]
+        this.fontVariant = 'normal';    // normal, small caps [not implemented]
+        this.fontWeight = 'normal'; // normal, bold, bolder, lighter, 100 - 900
+        this.fontSize = '10px'; 
+        this.fontFamily = 'sans-serif';
+        this.fontStretch = 1.0;
+        this.fillStyle = '#666666';
+        this.angle = 0;
+        this.textAlign = 'start';
+        this.textBaseline = 'alphabetic';
+        this.text;
+        this.width;
+        this.height;
+        this.pt2px = 1.28;
+
+        $.extend(true, this, options);
+        this.normalizedFontSize = this.normalizeFontSize(this.fontSize);
+        this.setHeight();
+    };
+    
+    $.jqplot.CanvasTextRenderer.prototype.init = function(options) {
+        $.extend(true, this, options);
+        this.normalizedFontSize = this.normalizeFontSize(this.fontSize);
+        this.setHeight();
+    };
+    
+    // convert css spec into point size
+    // returns float
+    $.jqplot.CanvasTextRenderer.prototype.normalizeFontSize = function(sz) {
+        sz = String(sz);
+        var n = parseFloat(sz);
+        if (sz.indexOf('px') > -1) {
+            return n/this.pt2px;
+        }
+        else if (sz.indexOf('pt') > -1) {
+            return n;
+        }
+        else if (sz.indexOf('em') > -1) {
+            return n*12;
+        }
+        else if (sz.indexOf('%') > -1) {
+            return n*12/100;
+        }
+        // default to pixels;
+        else {
+            return n/this.pt2px;
+        }
+    };
+    
+    
+    $.jqplot.CanvasTextRenderer.prototype.fontWeight2Float = function(w) {
+        // w = normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
+        // return values adjusted for Hershey font.
+        if (Number(w)) {
+            return w/400;
+        }
+        else {
+            switch (w) {
+                case 'normal':
+                    return 1;
+                    break;
+                case 'bold':
+                    return 1.75;
+                    break;
+                case 'bolder':
+                    return 2.25;
+                    break;
+                case 'lighter':
+                    return 0.75;
+                    break;
+                default:
+                    return 1;
+                    break;
+             }   
+        }
+    };
+    
+    $.jqplot.CanvasTextRenderer.prototype.getText = function() {
+        return this.text;
+    };
+    
+    $.jqplot.CanvasTextRenderer.prototype.setText = function(t, ctx) {
+        this.text = t;
+        this.setWidth(ctx);
+        return this;
+    };
+    
+    $.jqplot.CanvasTextRenderer.prototype.getWidth = function(ctx) {
+        return this.width;
+    };
+    
+    $.jqplot.CanvasTextRenderer.prototype.setWidth = function(ctx, w) {
+        if (!w) {
+            this.width = this.measure(ctx, this.text);
+        }
+        else {
+            this.width = w;   
+        }
+        return this;
+    };
+    
+    // return height in pixels.
+    $.jqplot.CanvasTextRenderer.prototype.getHeight = function(ctx) {
+        return this.height;
+    };
+    
+    // w - height in pt
+    // set heigh in px
+    $.jqplot.CanvasTextRenderer.prototype.setHeight = function(w) {
+        if (!w) {
+            //height = this.fontSize /0.75;
+            this.height = this.normalizedFontSize * this.pt2px;
+        }
+        else {
+            this.height = w;   
+        }
+        return this;
+    };
+
+    $.jqplot.CanvasTextRenderer.prototype.letter = function (ch)
+    {
+        return this.letters[ch];
+    };
+
+    $.jqplot.CanvasTextRenderer.prototype.ascent = function()
+    {
+        return this.normalizedFontSize;
+    };
+
+    $.jqplot.CanvasTextRenderer.prototype.descent = function()
+    {
+        return 7.0*this.normalizedFontSize/25.0;
+    };
+
+    $.jqplot.CanvasTextRenderer.prototype.measure = function(ctx, str)
+    {
+        var total = 0;
+        var len = str.length;
+ 
+        for (var i = 0; i < len; i++) {
+            var c = this.letter(str.charAt(i));
+            if (c) {
+                total += c.width * this.normalizedFontSize / 25.0 * this.fontStretch;
+            }
+        }
+        return total;
+    };
+
+    $.jqplot.CanvasTextRenderer.prototype.draw = function(ctx,str)
+    {
+        var x = 0;
+        // leave room at bottom for descenders.
+        var y = this.height*0.72;
+         var total = 0;
+         var len = str.length;
+         var mag = this.normalizedFontSize / 25.0;
+
+         ctx.save();
+         var tx, ty;
+         
+         // 1st quadrant
+         if ((-Math.PI/2 <= this.angle && this.angle <= 0) || (Math.PI*3/2 <= this.angle && this.angle <= Math.PI*2)) {
+             tx = 0;
+             ty = -Math.sin(this.angle) * this.width;
+         }
+         // 4th quadrant
+         else if ((0 < this.angle && this.angle <= Math.PI/2) || (-Math.PI*2 <= this.angle && this.angle <= -Math.PI*3/2)) {
+             tx = Math.sin(this.angle) * this.height;
+             ty = 0;
+         }
+         // 2nd quadrant
+         else if ((-Math.PI < this.angle && this.angle < -Math.PI/2) || (Math.PI <= this.angle && this.angle <= Math.PI*3/2)) {
+             tx = -Math.cos(this.angle) * this.width;
+             ty = -Math.sin(this.angle) * this.width - Math.cos(this.angle) * this.height;
+         }
+         // 3rd quadrant
+         else if ((-Math.PI*3/2 < this.angle && this.angle < Math.PI) || (Math.PI/2 < this.angle && this.angle < Math.PI)) {
+             tx = Math.sin(this.angle) * this.height - Math.cos(this.angle)*this.width;
+             ty = -Math.cos(this.angle) * this.height;
+         }
+         
+         ctx.strokeStyle = this.fillStyle;
+         ctx.fillStyle = this.fillStyle;
+         ctx.translate(tx, ty);
+         ctx.rotate(this.angle);
+         ctx.lineCap = "round";
+         // multiplier was 2.0
+         var fact = (this.normalizedFontSize > 30) ? 2.0 : 2 + (30 - this.normalizedFontSize)/20;
+         ctx.lineWidth = fact * mag * this.fontWeight2Float(this.fontWeight);
+         
+         for ( var i = 0; i < len; i++) {
+            var c = this.letter( str.charAt(i));
+            if ( !c) {
+                continue;
+            }
+
+            ctx.beginPath();
+
+            var penUp = 1;
+            var needStroke = 0;
+            for ( var j = 0; j < c.points.length; j++) {
+              var a = c.points[j];
+              if ( a[0] == -1 && a[1] == -1) {
+                  penUp = 1;
+                  continue;
+              }
+              if ( penUp) {
+                  ctx.moveTo( x + a[0]*mag*this.fontStretch, y - a[1]*mag);
+                  penUp = false;
+              } else {
+                  ctx.lineTo( x + a[0]*mag*this.fontStretch, y - a[1]*mag);
+              }
+            }
+            ctx.stroke();
+            x += c.width*mag*this.fontStretch;
+         }
+         ctx.restore();
+         return total;
+    };
+
+    $.jqplot.CanvasTextRenderer.prototype.letters = {
+         ' ': { width: 16, points: [] },
+         '!': { width: 10, points: [[5,21],[5,7],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] },
+         '"': { width: 16, points: [[4,21],[4,14],[-1,-1],[12,21],[12,14]] },
+         '#': { width: 21, points: [[11,25],[4,-7],[-1,-1],[17,25],[10,-7],[-1,-1],[4,12],[18,12],[-1,-1],[3,6],[17,6]] },
+         '$': { width: 20, points: [[8,25],[8,-4],[-1,-1],[12,25],[12,-4],[-1,-1],[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
+         '%': { width: 24, points: [[21,21],[3,0],[-1,-1],[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],[10,20],[13,19],[16,19],[19,20],[21,21],[-1,-1],[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] },
+         '&': { width: 26, points: [[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]] },
+         '\'': { width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] },
+         '(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] },
+         ')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] },
+         '*': { width: 16, points: [[8,21],[8,9],[-1,-1],[3,18],[13,12],[-1,-1],[13,18],[3,12]] },
+         '+': { width: 26, points: [[13,18],[13,0],[-1,-1],[4,9],[22,9]] },
+         ',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
+         '-': { width: 18, points: [[6,9],[12,9]] },
+         '.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] },
+         '/': { width: 22, points: [[20,25],[2,-7]] },
+         '0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] },
+         '1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] },
+         '2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] },
+         '3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
+         '4': { width: 20, points: [[13,21],[3,7],[18,7],[-1,-1],[13,21],[13,0]] },
+         '5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
+         '6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] },
+         '7': { width: 20, points: [[17,21],[7,0],[-1,-1],[3,21],[17,21]] },
+         '8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] },
+         '9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] },
+         ':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] },
+         ';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],[-1,-1],[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
+         '<': { width: 24, points: [[20,18],[4,9],[20,0]] },
+         '=': { width: 26, points: [[4,12],[22,12],[-1,-1],[4,6],[22,6]] },
+         '>': { width: 24, points: [[4,18],[20,9],[4,0]] },
+         '?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],[-1,-1],[9,2],[8,1],[9,0],[10,1],[9,2]] },
+         '@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],[-1,-1],[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],[-1,-1],[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],[-1,-1],[19,16],[18,8],[18,6],[19,5]] },
+         'A': { width: 18, points: [[9,21],[1,0],[-1,-1],[9,21],[17,0],[-1,-1],[4,7],[14,7]] },
+         'B': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[-1,-1],[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] },
+         'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] },
+         'D': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] },
+         'E': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11],[-1,-1],[4,0],[17,0]] },
+         'F': { width: 18, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11]] },
+         'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],[-1,-1],[13,8],[18,8]] },
+         'H': { width: 22, points: [[4,21],[4,0],[-1,-1],[18,21],[18,0],[-1,-1],[4,11],[18,11]] },
+         'I': { width: 8, points: [[4,21],[4,0]] },
+         'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] },
+         'K': { width: 21, points: [[4,21],[4,0],[-1,-1],[18,21],[4,7],[-1,-1],[9,12],[18,0]] },
+         'L': { width: 17, points: [[4,21],[4,0],[-1,-1],[4,0],[16,0]] },
+         'M': { width: 24, points: [[4,21],[4,0],[-1,-1],[4,21],[12,0],[-1,-1],[20,21],[12,0],[-1,-1],[20,21],[20,0]] },
+         'N': { width: 22, points: [[4,21],[4,0],[-1,-1],[4,21],[18,0],[-1,-1],[18,21],[18,0]] },
+         'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] },
+         'P': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] },
+         'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],[-1,-1],[12,4],[18,-2]] },
+         'R': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],[-1,-1],[11,11],[18,0]] },
+         'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
+         'T': { width: 16, points: [[8,21],[8,0],[-1,-1],[1,21],[15,21]] },
+         'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] },
+         'V': { width: 18, points: [[1,21],[9,0],[-1,-1],[17,21],[9,0]] },
+         'W': { width: 24, points: [[2,21],[7,0],[-1,-1],[12,21],[7,0],[-1,-1],[12,21],[17,0],[-1,-1],[22,21],[17,0]] },
+         'X': { width: 20, points: [[3,21],[17,0],[-1,-1],[17,21],[3,0]] },
+         'Y': { width: 18, points: [[1,21],[9,11],[9,0],[-1,-1],[17,21],[9,11]] },
+         'Z': { width: 20, points: [[17,21],[3,0],[-1,-1],[3,21],[17,21],[-1,-1],[3,0],[17,0]] },
+         '[': { width: 14, points: [[4,25],[4,-7],[-1,-1],[5,25],[5,-7],[-1,-1],[4,25],[11,25],[-1,-1],[4,-7],[11,-7]] },
+         '\\': { width: 14, points: [[0,21],[14,-3]] },
+         ']': { width: 14, points: [[9,25],[9,-7],[-1,-1],[10,25],[10,-7],[-1,-1],[3,25],[10,25],[-1,-1],[3,-7],[10,-7]] },
+         '^': { width: 16, points: [[6,15],[8,18],[10,15],[-1,-1],[3,12],[8,17],[13,12],[-1,-1],[8,17],[8,0]] },
+         '_': { width: 16, points: [[0,-2],[16,-2]] },
+         '`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] },
+         'a': { width: 19, points: [[15,14],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+         'b': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
+         'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+         'd': { width: 19, points: [[15,21],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+         'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+         'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],[-1,-1],[2,14],[9,14]] },
+         'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+         'h': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
+         'i': { width: 8, points: [[3,21],[4,20],[5,21],[4,22],[3,21],[-1,-1],[4,14],[4,0]] },
+         'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],[-1,-1],[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] },
+         'k': { width: 17, points: [[4,21],[4,0],[-1,-1],[14,14],[4,4],[-1,-1],[8,8],[15,0]] },
+         'l': { width: 8, points: [[4,21],[4,0]] },
+         'm': { width: 30, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],[-1,-1],[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] },
+         'n': { width: 19, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
+         'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] },
+         'p': { width: 19, points: [[4,14],[4,-7],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
+         'q': { width: 19, points: [[15,14],[15,-7],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
+         'r': { width: 13, points: [[4,14],[4,0],[-1,-1],[4,8],[5,11],[7,13],[9,14],[12,14]] },
+         's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] },
+         't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],[-1,-1],[2,14],[9,14]] },
+         'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],[-1,-1],[15,14],[15,0]] },
+         'v': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0]] },
+         'w': { width: 22, points: [[3,14],[7,0],[-1,-1],[11,14],[7,0],[-1,-1],[11,14],[15,0],[-1,-1],[19,14],[15,0]] },
+         'x': { width: 17, points: [[3,14],[14,0],[-1,-1],[14,14],[3,0]] },
+         'y': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] },
+         'z': { width: 17, points: [[14,14],[3,0],[-1,-1],[3,14],[14,14],[-1,-1],[3,0],[14,0]] },
+         '{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],[-1,-1],[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],[-1,-1],[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] },
+         '|': { width: 8, points: [[4,25],[4,-7]] },
+         '}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],[-1,-1],[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],[-1,-1],[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] },
+         '~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],[-1,-1],[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] }
+     };
+     
+    $.jqplot.CanvasFontRenderer = function(options) {
+        options = options || {};
+        if (!options.pt2px) {
+            options.pt2px = 1.5;
+        }
+        $.jqplot.CanvasTextRenderer.call(this, options);
+    };
+    
+    $.jqplot.CanvasFontRenderer.prototype = new $.jqplot.CanvasTextRenderer({});
+    $.jqplot.CanvasFontRenderer.prototype.constructor = $.jqplot.CanvasFontRenderer;
+
+    $.jqplot.CanvasFontRenderer.prototype.measure = function(ctx, str)
+    {
+        // var fstyle = this.fontStyle+' '+this.fontVariant+' '+this.fontWeight+' '+this.fontSize+' '+this.fontFamily;
+        var fstyle = this.fontSize+' '+this.fontFamily;
+        ctx.save();
+        ctx.font = fstyle;
+        var w = ctx.measureText(str).width;
+        ctx.restore();
+        return w;
+    };
+
+    $.jqplot.CanvasFontRenderer.prototype.draw = function(ctx, str)
+    {
+        var x = 0;
+        // leave room at bottom for descenders.
+        var y = this.height*0.72;
+        //var y = 12;
+
+         ctx.save();
+         var tx, ty;
+         
+         // 1st quadrant
+         if ((-Math.PI/2 <= this.angle && this.angle <= 0) || (Math.PI*3/2 <= this.angle && this.angle <= Math.PI*2)) {
+             tx = 0;
+             ty = -Math.sin(this.angle) * this.width;
+         }
+         // 4th quadrant
+         else if ((0 < this.angle && this.angle <= Math.PI/2) || (-Math.PI*2 <= this.angle && this.angle <= -Math.PI*3/2)) {
+             tx = Math.sin(this.angle) * this.height;
+             ty = 0;
+         }
+         // 2nd quadrant
+         else if ((-Math.PI < this.angle && this.angle < -Math.PI/2) || (Math.PI <= this.angle && this.angle <= Math.PI*3/2)) {
+             tx = -Math.cos(this.angle) * this.width;
+             ty = -Math.sin(this.angle) * this.width - Math.cos(this.angle) * this.height;
+         }
+         // 3rd quadrant
+         else if ((-Math.PI*3/2 < this.angle && this.angle < Math.PI) || (Math.PI/2 < this.angle && this.angle < Math.PI)) {
+             tx = Math.sin(this.angle) * this.height - Math.cos(this.angle)*this.width;
+             ty = -Math.cos(this.angle) * this.height;
+         }
+         ctx.strokeStyle = this.fillStyle;
+         ctx.fillStyle = this.fillStyle;
+        // var fstyle = this.fontStyle+' '+this.fontVariant+' '+this.fontWeight+' '+this.fontSize+' '+this.fontFamily;
+        var fstyle = this.fontSize+' '+this.fontFamily;
+         ctx.font = fstyle;
+         ctx.translate(tx, ty);
+         ctx.rotate(this.angle);
+         ctx.fillText(str, x, y);
+         // ctx.strokeText(str, x, y);
+
+         ctx.restore();
+    };
+    
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.categoryAxisRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.categoryAxisRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.categoryAxisRenderer.js	(revision 2119)
@@ -0,0 +1,679 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {   
+    /**
+    *  class: $.jqplot.CategoryAxisRenderer
+    *  A plugin for jqPlot to render a category style axis, with equal pixel spacing between y data values of a series.
+    *  
+    *  To use this renderer, include the plugin in your source
+    *  > <script type="text/javascript" language="javascript" src="plugins/jqplot.categoryAxisRenderer.js"></script>
+    *  
+    *  and supply the appropriate options to your plot
+    *  
+    *  > {axes:{xaxis:{renderer:$.jqplot.CategoryAxisRenderer}}}
+    **/
+    $.jqplot.CategoryAxisRenderer = function(options) {
+        $.jqplot.LinearAxisRenderer.call(this);
+        // prop: sortMergedLabels
+        // True to sort tick labels when labels are created by merging
+        // x axis values from multiple series.  That is, say you have
+        // two series like:
+        // > line1 = [[2006, 4],            [2008, 9], [2009, 16]];
+        // > line2 = [[2006, 3], [2007, 7], [2008, 6]];
+        // If no label array is specified, tick labels will be collected
+        // from the x values of the series.  With sortMergedLabels
+        // set to true, tick labels will be:
+        // > [2006, 2007, 2008, 2009]
+        // With sortMergedLabels set to false, tick labels will be:
+        // > [2006, 2008, 2009, 2007]
+        //
+        // Note, this property is specified on the renderOptions for the 
+        // axes when creating a plot:
+        // > axes:{xaxis:{renderer:$.jqplot.CategoryAxisRenderer, rendererOptions:{sortMergedLabels:true}}}
+        this.sortMergedLabels = false;
+    };
+    
+    $.jqplot.CategoryAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+    $.jqplot.CategoryAxisRenderer.prototype.constructor = $.jqplot.CategoryAxisRenderer;
+    
+    $.jqplot.CategoryAxisRenderer.prototype.init = function(options){
+        this.groups = 1;
+        this.groupLabels = [];
+        this._groupLabels = [];
+        this._grouped = false;
+        this._barsPerGroup = null;
+        this.reverse = false;
+        // prop: tickRenderer
+        // A class of a rendering engine for creating the ticks labels displayed on the plot, 
+        // See <$.jqplot.AxisTickRenderer>.
+        // this.tickRenderer = $.jqplot.AxisTickRenderer;
+        // this.labelRenderer = $.jqplot.AxisLabelRenderer;
+        $.extend(true, this, {tickOptions:{formatString:'%d'}}, options);
+        var db = this._dataBounds;
+        // Go through all the series attached to this axis and find
+        // the min/max bounds for this axis.
+        for (var i=0; i<this._series.length; i++) {
+            var s = this._series[i];
+            if (s.groups) {
+                this.groups = s.groups;
+            }
+            var d = s.data;
+            
+            for (var j=0; j<d.length; j++) { 
+                if (this.name == 'xaxis' || this.name == 'x2axis') {
+                    if (d[j][0] < db.min || db.min == null) {
+                        db.min = d[j][0];
+                    }
+                    if (d[j][0] > db.max || db.max == null) {
+                        db.max = d[j][0];
+                    }
+                }              
+                else {
+                    if (d[j][1] < db.min || db.min == null) {
+                        db.min = d[j][1];
+                    }
+                    if (d[j][1] > db.max || db.max == null) {
+                        db.max = d[j][1];
+                    }
+                }              
+            }
+        }
+        
+        if (this.groupLabels.length) {
+            this.groups = this.groupLabels.length;
+        }
+    };
+ 
+
+    $.jqplot.CategoryAxisRenderer.prototype.createTicks = function() {
+        // we're are operating on an axis here
+        var ticks = this._ticks;
+        var userTicks = this.ticks;
+        var name = this.name;
+        // databounds were set on axis initialization.
+        var db = this._dataBounds;
+        var dim, interval;
+        var min, max;
+        var pos1, pos2;
+        var tt, i;
+
+        // if we already have ticks, use them.
+        if (userTicks.length) {
+            // adjust with blanks if we have groups
+            if (this.groups > 1 && !this._grouped) {
+                var l = userTicks.length;
+                var skip = parseInt(l/this.groups, 10);
+                var count = 0;
+                for (var i=skip; i<l; i+=skip) {
+                    userTicks.splice(i+count, 0, ' ');
+                    count++;
+                }
+                this._grouped = true;
+            }
+            this.min = 0.5;
+            this.max = userTicks.length + 0.5;
+            var range = this.max - this.min;
+            this.numberTicks = 2*userTicks.length + 1;
+            for (i=0; i<userTicks.length; i++){
+                tt = this.min + 2 * i * range / (this.numberTicks-1);
+                // need a marker before and after the tick
+                var t = new this.tickRenderer(this.tickOptions);
+                t.showLabel = false;
+                // t.showMark = true;
+                t.setTick(tt, this.name);
+                this._ticks.push(t);
+                var t = new this.tickRenderer(this.tickOptions);
+                t.label = userTicks[i];
+                // t.showLabel = true;
+                t.showMark = false;
+                t.showGridline = false;
+                t.setTick(tt+0.5, this.name);
+                this._ticks.push(t);
+            }
+            // now add the last tick at the end
+            var t = new this.tickRenderer(this.tickOptions);
+            t.showLabel = false;
+            // t.showMark = true;
+            t.setTick(tt+1, this.name);
+            this._ticks.push(t);
+        }
+
+        // we don't have any ticks yet, let's make some!
+        else {
+            if (name == 'xaxis' || name == 'x2axis') {
+                dim = this._plotDimensions.width;
+            }
+            else {
+                dim = this._plotDimensions.height;
+            }
+            
+            // if min, max and number of ticks specified, user can't specify interval.
+            if (this.min != null && this.max != null && this.numberTicks != null) {
+                this.tickInterval = null;
+            }
+            
+            // if max, min, and interval specified and interval won't fit, ignore interval.
+            if (this.min != null && this.max != null && this.tickInterval != null) {
+                if (parseInt((this.max-this.min)/this.tickInterval, 10) != (this.max-this.min)/this.tickInterval) {
+                    this.tickInterval = null;
+                }
+            }
+        
+            // find out how many categories are in the lines and collect labels
+            var labels = [];
+            var numcats = 0;
+            var min = 0.5;
+            var max, val;
+            var isMerged = false;
+            for (var i=0; i<this._series.length; i++) {
+                var s = this._series[i];
+                for (var j=0; j<s.data.length; j++) {
+                    if (this.name == 'xaxis' || this.name == 'x2axis') {
+                        val = s.data[j][0];
+                    }
+                    else {
+                        val = s.data[j][1];
+                    }
+                    if ($.inArray(val, labels) == -1) {
+                        isMerged = true;
+                        numcats += 1;      
+                        labels.push(val);
+                    }
+                }
+            }
+            
+            if (isMerged && this.sortMergedLabels) {
+                if (typeof labels[0] == "string") {
+                    labels.sort();
+                } else {
+                    labels.sort(function(a,b) { return a - b; });
+                }
+            }
+            
+            // keep a reference to these tick labels to use for redrawing plot (see bug #57)
+            this.ticks = labels;
+            
+            // now bin the data values to the right lables.
+            for (var i=0; i<this._series.length; i++) {
+                var s = this._series[i];
+                for (var j=0; j<s.data.length; j++) {
+                    if (this.name == 'xaxis' || this.name == 'x2axis') {
+                        val = s.data[j][0];
+                    }
+                    else {
+                        val = s.data[j][1];
+                    }
+                    // for category axis, force the values into category bins.
+                    // we should have the value in the label array now.
+                    var idx = $.inArray(val, labels)+1;
+                    if (this.name == 'xaxis' || this.name == 'x2axis') {
+                        s.data[j][0] = idx;
+                    }
+                    else {
+                        s.data[j][1] = idx;
+                    }
+                }
+            }
+            
+            // adjust with blanks if we have groups
+            if (this.groups > 1 && !this._grouped) {
+                var l = labels.length;
+                var skip = parseInt(l/this.groups, 10);
+                var count = 0;
+                for (var i=skip; i<l; i+=skip+1) {
+                    labels[i] = ' ';
+                }
+                this._grouped = true;
+            }
+        
+            max = numcats + 0.5;
+            if (this.numberTicks == null) {
+                this.numberTicks = 2*numcats + 1;
+            }
+
+            var range = max - min;
+            this.min = min;
+            this.max = max;
+            var track = 0;
+            
+            // todo: adjust this so more ticks displayed.
+            var maxVisibleTicks = parseInt(3+dim/10, 10);
+            var skip = parseInt(numcats/maxVisibleTicks, 10);
+
+            if (this.tickInterval == null) {
+
+                this.tickInterval = range / (this.numberTicks-1);
+
+            }
+            // if tickInterval is specified, we will ignore any computed maximum.
+            for (var i=0; i<this.numberTicks; i++){
+                tt = this.min + i * this.tickInterval;
+                var t = new this.tickRenderer(this.tickOptions);
+                // if even tick, it isn't a category, it's a divider
+                if (i/2 == parseInt(i/2, 10)) {
+                    t.showLabel = false;
+                    t.showMark = true;
+                }
+                else {
+                    if (skip>0 && track<skip) {
+                        t.showLabel = false;
+                        track += 1;
+                    }
+                    else {
+                        t.showLabel = true;
+                        track = 0;
+                    } 
+                    t.label = t.formatter(t.formatString, labels[(i-1)/2]);
+                    t.showMark = false;
+                    t.showGridline = false;
+                }
+                t.setTick(tt, this.name);
+                this._ticks.push(t);
+            }
+        }
+        
+    };
+    
+    // called with scope of axis
+    $.jqplot.CategoryAxisRenderer.prototype.draw = function(ctx, plot) {
+        if (this.show) {
+            // populate the axis label and value properties.
+            // createTicks is a method on the renderer, but
+            // call it within the scope of the axis.
+            this.renderer.createTicks.call(this);
+            // fill a div with axes labels in the right direction.
+            // Need to pregenerate each axis to get its bounds and
+            // position it and the labels correctly on the plot.
+            var dim=0;
+            var temp;
+            // Added for theming.
+            if (this._elem) {
+                // this._elem.empty();
+                // Memory Leaks patch
+                this._elem.emptyForce();
+            }
+
+            this._elem = this._elem || $('<div class="jqplot-axis jqplot-'+this.name+'" style="position:absolute;"></div>');
+            
+            if (this.name == 'xaxis' || this.name == 'x2axis') {
+                this._elem.width(this._plotDimensions.width);
+            }
+            else {
+                this._elem.height(this._plotDimensions.height);
+            }
+            
+            // create a _label object.
+            this.labelOptions.axis = this.name;
+            this._label = new this.labelRenderer(this.labelOptions);
+            if (this._label.show) {
+                var elem = this._label.draw(ctx, plot);
+                elem.appendTo(this._elem);
+            }
+    
+            var t = this._ticks;
+            for (var i=0; i<t.length; i++) {
+                var tick = t[i];
+                if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
+                    var elem = tick.draw(ctx, plot);
+                    elem.appendTo(this._elem);
+                }
+            }
+        
+            this._groupLabels = [];
+            // now make group labels
+            for (var i=0; i<this.groupLabels.length; i++)
+            {
+                var elem = $('<div style="position:absolute;" class="jqplot-'+this.name+'-groupLabel"></div>');
+                elem.html(this.groupLabels[i]);
+                this._groupLabels.push(elem);
+                elem.appendTo(this._elem);
+            }
+        }
+        return this._elem;
+    };
+    
+    // called with scope of axis
+    $.jqplot.CategoryAxisRenderer.prototype.set = function() { 
+        var dim = 0;
+        var temp;
+        var w = 0;
+        var h = 0;
+        var lshow = (this._label == null) ? false : this._label.show;
+        if (this.show) {
+            var t = this._ticks;
+            for (var i=0; i<t.length; i++) {
+                var tick = t[i];
+                if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
+                    if (this.name == 'xaxis' || this.name == 'x2axis') {
+                        temp = tick._elem.outerHeight(true);
+                    }
+                    else {
+                        temp = tick._elem.outerWidth(true);
+                    }
+                    if (temp > dim) {
+                        dim = temp;
+                    }
+                }
+            }
+            
+            var dim2 = 0;
+            for (var i=0; i<this._groupLabels.length; i++) {
+                var l = this._groupLabels[i];
+                if (this.name == 'xaxis' || this.name == 'x2axis') {
+                    temp = l.outerHeight(true);
+                }
+                else {
+                    temp = l.outerWidth(true);
+                }
+                if (temp > dim2) {
+                    dim2 = temp;
+                }
+            }
+            
+            if (lshow) {
+                w = this._label._elem.outerWidth(true);
+                h = this._label._elem.outerHeight(true); 
+            }
+            if (this.name == 'xaxis') {
+                dim += dim2 + h;
+                this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
+            }
+            else if (this.name == 'x2axis') {
+                dim += dim2 + h;
+                this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
+            }
+            else if (this.name == 'yaxis') {
+                dim += dim2 + w;
+                this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
+                if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+                    this._label._elem.css('width', w+'px');
+                }
+            }
+            else {
+                dim += dim2 + w;
+                this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
+                if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+                    this._label._elem.css('width', w+'px');
+                }
+            }
+        }  
+    };
+    
+    // called with scope of axis
+    $.jqplot.CategoryAxisRenderer.prototype.pack = function(pos, offsets) {
+        var ticks = this._ticks;
+        var max = this.max;
+        var min = this.min;
+        var offmax = offsets.max;
+        var offmin = offsets.min;
+        var lshow = (this._label == null) ? false : this._label.show;
+        var i;
+
+        for (var p in pos) {
+            this._elem.css(p, pos[p]);
+        }
+        
+        this._offsets = offsets;
+        // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
+        var pixellength = offmax - offmin;
+        var unitlength = max - min;
+        
+        if (!this.reverse) {
+            // point to unit and unit to point conversions references to Plot DOM element top left corner.
+            
+            this.u2p = function(u){
+                return (u - min) * pixellength / unitlength + offmin;
+            };
+
+            this.p2u = function(p){
+                return (p - offmin) * unitlength / pixellength + min;
+            };
+                    
+            if (this.name == 'xaxis' || this.name == 'x2axis'){
+                this.series_u2p = function(u){
+                    return (u - min) * pixellength / unitlength;
+                };
+                this.series_p2u = function(p){
+                    return p * unitlength / pixellength + min;
+                };
+            }
+            
+            else {
+                this.series_u2p = function(u){
+                    return (u - max) * pixellength / unitlength;
+                };
+                this.series_p2u = function(p){
+                    return p * unitlength / pixellength + max;
+                };
+            }
+        }
+
+        else {
+            // point to unit and unit to point conversions references to Plot DOM element top left corner.
+            
+            this.u2p = function(u){
+                return offmin + (max - u) * pixellength / unitlength;
+            };
+
+            this.p2u = function(p){
+                return min + (p - offmin) * unitlength / pixellength;
+            };
+                    
+            if (this.name == 'xaxis' || this.name == 'x2axis'){
+                this.series_u2p = function(u){
+                    return (max - u) * pixellength / unitlength;
+                };
+                this.series_p2u = function(p){
+                    return p * unitlength / pixellength + max;
+                };
+            }
+            
+            else {
+                this.series_u2p = function(u){
+                    return (min - u) * pixellength / unitlength;
+                };
+                this.series_p2u = function(p){
+                    return p * unitlength / pixellength + min;
+                };
+            }
+
+        }
+            
+        
+        if (this.show) {
+            if (this.name == 'xaxis' || this.name == 'x2axis') {
+                for (i=0; i<ticks.length; i++) {
+                    var t = ticks[i];
+                    if (t.show && t.showLabel) {
+                        var shim;
+                        
+                        if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+                            // will need to adjust auto positioning based on which axis this is.
+                            var temp = (this.name == 'xaxis') ? 1 : -1;
+                            switch (t.labelPosition) {
+                                case 'auto':
+                                    // position at end
+                                    if (temp * t.angle < 0) {
+                                        shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    }
+                                    // position at start
+                                    else {
+                                        shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'end':
+                                    shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                                case 'start':
+                                    shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    break;
+                                case 'middle':
+                                    shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                                default:
+                                    shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                            }
+                        }
+                        else {
+                            shim = -t.getWidth()/2;
+                        }
+                        var val = this.u2p(t.value) + shim + 'px';
+                        t._elem.css('left', val);
+                        t.pack();
+                    }
+                }
+                
+                var labeledge=['bottom', 0];
+                if (lshow) {
+                    var w = this._label._elem.outerWidth(true);
+                    this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
+                    if (this.name == 'xaxis') {
+                        this._label._elem.css('bottom', '0px');
+                        labeledge = ['bottom', this._label._elem.outerHeight(true)];
+                    }
+                    else {
+                        this._label._elem.css('top', '0px');
+                        labeledge = ['top', this._label._elem.outerHeight(true)];
+                    }
+                    this._label.pack();
+                }
+                
+                // draw the group labels
+                var step = parseInt(this._ticks.length/this.groups, 10) + 1;
+                for (i=0; i<this._groupLabels.length; i++) {
+                    var mid = 0;
+                    var count = 0;
+                    for (var j=i*step; j<(i+1)*step; j++) {
+                        if (j >= this._ticks.length-1) continue; // the last tick does not exist as there is no other group in order to have an empty one.
+                        if (this._ticks[j]._elem && this._ticks[j].label != " ") {
+                            var t = this._ticks[j]._elem;
+                            var p = t.position();
+                            mid += p.left + t.outerWidth(true)/2;
+                            count++;
+                        }
+                    }
+                    mid = mid/count;
+                    this._groupLabels[i].css({'left':(mid - this._groupLabels[i].outerWidth(true)/2)});
+                    this._groupLabels[i].css(labeledge[0], labeledge[1]);
+                }
+            }
+            else {
+                for (i=0; i<ticks.length; i++) {
+                    var t = ticks[i];
+                    if (t.show && t.showLabel) {                        
+                        var shim;
+                        if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+                            // will need to adjust auto positioning based on which axis this is.
+                            var temp = (this.name == 'yaxis') ? 1 : -1;
+                            switch (t.labelPosition) {
+                                case 'auto':
+                                    // position at end
+                                case 'end':
+                                    if (temp * t.angle < 0) {
+                                        shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+                                    }
+                                    else {
+                                        shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'start':
+                                    if (t.angle > 0) {
+                                        shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+                                    }
+                                    else {
+                                        shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'middle':
+                                    // if (t.angle > 0) {
+                                    //     shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    // }
+                                    // else {
+                                    //     shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    // }
+                                    shim = -t.getHeight()/2;
+                                    break;
+                                default:
+                                    shim = -t.getHeight()/2;
+                                    break;
+                            }
+                        }
+                        else {
+                            shim = -t.getHeight()/2;
+                        }
+                        
+                        var val = this.u2p(t.value) + shim + 'px';
+                        t._elem.css('top', val);
+                        t.pack();
+                    }
+                }
+                
+                var labeledge=['left', 0];
+                if (lshow) {
+                    var h = this._label._elem.outerHeight(true);
+                    this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
+                    if (this.name == 'yaxis') {
+                        this._label._elem.css('left', '0px');
+                        labeledge = ['left', this._label._elem.outerWidth(true)];
+                    }
+                    else {
+                        this._label._elem.css('right', '0px');
+                        labeledge = ['right', this._label._elem.outerWidth(true)];
+                    }   
+                    this._label.pack();
+                }
+                
+                // draw the group labels, position top here, do left after label position.
+                var step = parseInt(this._ticks.length/this.groups, 10) + 1; // step is one more than before as we don't want to have overlaps in loops
+                for (i=0; i<this._groupLabels.length; i++) {
+                    var mid = 0;
+                    var count = 0;
+                    for (var j=i*step; j<(i+1)*step; j++) { // j must never reach (i+1)*step as we don't want to have overlap between loops
+                        if (j >= this._ticks.length-1) continue; // the last tick does not exist as there is no other group in order to have an empty one.
+                        if (this._ticks[j]._elem && this._ticks[j].label != " ") {
+                            var t = this._ticks[j]._elem;
+                            var p = t.position();
+                            mid += p.top + t.outerHeight()/2;
+                            count++;
+                        }
+                    }
+                    mid = mid/count;
+                    this._groupLabels[i].css({'top':mid - this._groupLabels[i].outerHeight()/2});
+                    this._groupLabels[i].css(labeledge[0], labeledge[1]);
+                    
+                }
+            }
+        }
+    };    
+    
+    
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.ciParser.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.ciParser.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.ciParser.js	(revision 2119)
@@ -0,0 +1,116 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    /**
+     * Class: $.jqplot.ciParser
+     * Data Renderer function which converts a custom JSON data object into jqPlot data format.
+     * Set this as a callable on the jqplot dataRenderer plot option:
+     * 
+     * > plot = $.jqplot('mychart', [data], { dataRenderer: $.jqplot.ciParser, ... });
+     * 
+     * Where data is an object in JSON format or a JSON encoded string conforming to the
+     * City Index API spec.
+     * 
+     * Note that calling the renderer function is handled internally by jqPlot.  The
+     * user does not have to call the function.  The parameters described below will
+     * automatically be passed to the ciParser function.
+     * 
+     * Parameters:
+     * data - JSON encoded string or object.
+     * plot - reference to jqPlot Plot object.
+     * 
+     * Returns:
+     * data array in jqPlot format.
+     * 
+     */
+    $.jqplot.ciParser = function (data, plot) {
+        var ret = [],
+            line,
+            temp,
+            i, j, k, kk;
+    
+         if (typeof(data) == "string") {
+             data =  $.jqplot.JSON.parse(data, handleStrings);
+         }
+ 
+         else if (typeof(data) == "object") {
+             for (k in data) {
+                 for (i=0; i<data[k].length; i++) {
+                     for (kk in data[k][i]) {
+                         data[k][i][kk] = handleStrings(kk, data[k][i][kk]);
+                     }
+                 }
+             }
+         }
+ 
+         else {
+             return null;
+         }
+ 
+         // function handleStrings
+         // Checks any JSON encoded strings to see if they are
+         // encoded dates.  If so, pull out the timestamp.
+         // Expects dates to be represented by js timestamps.
+ 
+         function handleStrings(key, value) {
+            var a;
+            if (value != null) {
+                if (value.toString().indexOf('Date') >= 0) {
+                    //here we will try to extract the ticks from the Date string in the "value" fields of JSON returned data
+                    a = /^\/Date\((-?[0-9]+)\)\/$/.exec(value);
+                    if (a) {
+                        return parseInt(a[1], 10);
+                    }
+                }
+                return value;
+            }
+         }
+ 
+        for (var prop in data) {
+            line = [];
+            temp = data[prop];
+            switch (prop) {
+                case "PriceTicks":
+                    for (i=0; i<temp.length; i++) {
+                        line.push([temp[i]['TickDate'], temp[i]['Price']]);
+                    }
+                    break;
+                case "PriceBars":
+                    for (i=0; i<temp.length; i++) {
+                        line.push([temp[i]['BarDate'], temp[i]['Open'], temp[i]['High'], temp[i]['Low'], temp[i]['Close']]);
+                    }
+                    break;
+            }
+            ret.push(line);
+        }
+        return ret;
+    };
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.cursor.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.cursor.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.cursor.js	(revision 2119)
@@ -0,0 +1,1108 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    
+    /**
+     * Class: $.jqplot.Cursor
+     * Plugin class representing the cursor as displayed on the plot.
+     */
+    $.jqplot.Cursor = function(options) {
+        // Group: Properties
+        //
+        // prop: style
+        // CSS spec for cursor style
+        this.style = 'crosshair';
+        this.previousCursor = 'auto';
+        // prop: show
+        // whether to show the cursor or not.
+        this.show = $.jqplot.config.enablePlugins;
+        // prop: showTooltip
+        // show a cursor position tooltip.  Location of the tooltip
+        // will be controlled by followMouse and tooltipLocation.
+        this.showTooltip = true;
+        // prop: followMouse
+        // Tooltip follows the mouse, it is not at a fixed location.
+        // Tooltip will show on the grid at the location given by
+        // tooltipLocation, offset from the grid edge by tooltipOffset.
+        this.followMouse = false;
+        // prop: tooltipLocation
+        // Where to position tooltip.  If followMouse is true, this is
+        // relative to the cursor, otherwise, it is relative to the grid.
+        // One of 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
+        this.tooltipLocation = 'se';
+        // prop: tooltipOffset
+        // Pixel offset of tooltip from the grid boudaries or cursor center.
+        this.tooltipOffset = 6;
+        // prop: showTooltipGridPosition
+        // show the grid pixel coordinates of the mouse.
+        this.showTooltipGridPosition = false;
+        // prop: showTooltipUnitPosition
+        // show the unit (data) coordinates of the mouse.
+        this.showTooltipUnitPosition = true;
+        // prop: showTooltipDataPosition
+        // Used with showVerticalLine to show intersecting data points in the tooltip.
+        this.showTooltipDataPosition = false;
+        // prop: tooltipFormatString
+        // sprintf format string for the tooltip.
+        // Uses Ash Searle's javascript sprintf implementation
+        // found here: http://hexmen.com/blog/2007/03/printf-sprintf/
+        // See http://perldoc.perl.org/functions/sprintf.html for reference
+        // Note, if showTooltipDataPosition is true, the default tooltipFormatString
+        // will be set to the cursorLegendFormatString, not the default given here.
+        this.tooltipFormatString = '%.4P, %.4P';
+        // prop: useAxesFormatters
+        // Use the x and y axes formatters to format the text in the tooltip.
+        this.useAxesFormatters = true;
+        // prop: tooltipAxisGroups
+        // Show position for the specified axes.
+        // This is an array like [['xaxis', 'yaxis'], ['xaxis', 'y2axis']]
+        // Default is to compute automatically for all visible axes.
+        this.tooltipAxisGroups = [];
+        // prop: zoom
+        // Enable plot zooming.
+        this.zoom = false;
+        // zoomProxy and zoomTarget properties are not directly set by user.  
+        // They Will be set through call to zoomProxy method.
+        this.zoomProxy = false;
+        this.zoomTarget = false;
+        // prop: looseZoom
+        // Will expand zoom range to provide more rounded tick values.
+        // Works only with linear, log and date axes.
+        this.looseZoom = true;
+        // prop: clickReset
+        // Will reset plot zoom if single click on plot without drag.
+        this.clickReset = false;
+        // prop: dblClickReset
+        // Will reset plot zoom if double click on plot without drag.
+        this.dblClickReset = true;
+        // prop: showVerticalLine
+        // draw a vertical line across the plot which follows the cursor.
+        // When the line is near a data point, a special legend and/or tooltip can
+        // be updated with the data values.
+        this.showVerticalLine = false;
+        // prop: showHorizontalLine
+        // draw a horizontal line across the plot which follows the cursor.
+        this.showHorizontalLine = false;
+        // prop: constrainZoomTo
+        // 'none', 'x' or 'y'
+        this.constrainZoomTo = 'none';
+        // // prop: autoscaleConstraint
+        // // when a constrained axis is specified, true will
+        // // auatoscale the adjacent axis.
+        // this.autoscaleConstraint = true;
+        this.shapeRenderer = new $.jqplot.ShapeRenderer();
+        this._zoom = {start:[], end:[], started: false, zooming:false, isZoomed:false, axes:{start:{}, end:{}}, gridpos:{}, datapos:{}};
+        this._tooltipElem;
+        this.zoomCanvas;
+        this.cursorCanvas;
+        // prop: intersectionThreshold
+        // pixel distance from data point or marker to consider cursor lines intersecting with point.
+        // If data point markers are not shown, this should be >= 1 or will often miss point intersections.
+        this.intersectionThreshold = 2;
+        // prop: showCursorLegend
+        // Replace the plot legend with an enhanced legend displaying intersection information.
+        this.showCursorLegend = false;
+        // prop: cursorLegendFormatString
+        // Format string used in the cursor legend.  If showTooltipDataPosition is true,
+        // this will also be the default format string used by tooltipFormatString.
+        this.cursorLegendFormatString = $.jqplot.Cursor.cursorLegendFormatString;
+        // whether the cursor is over the grid or not.
+        this._oldHandlers = {onselectstart: null, ondrag: null, onmousedown: null};
+        // prop: constrainOutsideZoom
+        // True to limit actual zoom area to edges of grid, even when zooming
+        // outside of plot area.  That is, can't zoom out by mousing outside plot.
+        this.constrainOutsideZoom = true;
+        // prop: showTooltipOutsideZoom
+        // True will keep updating the tooltip when zooming of the grid.
+        this.showTooltipOutsideZoom = false;
+        // true if mouse is over grid, false if not.
+        this.onGrid = false;
+        $.extend(true, this, options);
+    };
+    
+    $.jqplot.Cursor.cursorLegendFormatString = '%s x:%s, y:%s';
+    
+    // called with scope of plot
+    $.jqplot.Cursor.init = function (target, data, opts){
+        // add a cursor attribute to the plot
+        var options = opts || {};
+        this.plugins.cursor = new $.jqplot.Cursor(options.cursor);
+        var c = this.plugins.cursor;
+
+        if (c.show) {
+            $.jqplot.eventListenerHooks.push(['jqplotMouseEnter', handleMouseEnter]);
+            $.jqplot.eventListenerHooks.push(['jqplotMouseLeave', handleMouseLeave]);
+            $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMouseMove]);
+            
+            if (c.showCursorLegend) {              
+                opts.legend = opts.legend || {};
+                opts.legend.renderer =  $.jqplot.CursorLegendRenderer;
+                opts.legend.formatString = this.plugins.cursor.cursorLegendFormatString;
+                opts.legend.show = true;
+            }
+            
+            if (c.zoom) {
+                $.jqplot.eventListenerHooks.push(['jqplotMouseDown', handleMouseDown]);
+                
+                if (c.clickReset) {
+                    $.jqplot.eventListenerHooks.push(['jqplotClick', handleClick]);
+                }
+                
+                if (c.dblClickReset) {
+                    $.jqplot.eventListenerHooks.push(['jqplotDblClick', handleDblClick]);
+                }             
+            }
+    
+            this.resetZoom = function() {
+                var axes = this.axes;
+                if (!c.zoomProxy) {
+                    for (var ax in axes) {
+                        axes[ax].reset();
+                        axes[ax]._ticks = [];
+                        // fake out tick creation algorithm to make sure original auto
+                        // computed format string is used if _overrideFormatString is true
+                        if (c._zoom.axes[ax] !== undefined) {
+                            axes[ax]._autoFormatString = c._zoom.axes[ax].tickFormatString;
+                        }
+                    }
+                    this.redraw();
+                }
+                else {
+                    var ctx = this.plugins.cursor.zoomCanvas._ctx;
+                    ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+                    ctx = null;
+                }
+                this.plugins.cursor._zoom.isZoomed = false;
+                this.target.trigger('jqplotResetZoom', [this, this.plugins.cursor]);
+            };
+            
+
+            if (c.showTooltipDataPosition) {
+                c.showTooltipUnitPosition = false;
+                c.showTooltipGridPosition = false;
+                if (options.cursor.tooltipFormatString == undefined) {
+                    c.tooltipFormatString = $.jqplot.Cursor.cursorLegendFormatString;
+                }
+            }
+        }
+    };
+    
+    // called with context of plot
+    $.jqplot.Cursor.postDraw = function() {
+        var c = this.plugins.cursor;
+        
+        // Memory Leaks patch
+        if (c.zoomCanvas) {
+            c.zoomCanvas.resetCanvas();
+            c.zoomCanvas = null;
+        }
+        
+        if (c.cursorCanvas) {
+            c.cursorCanvas.resetCanvas();
+            c.cursorCanvas = null;
+        }
+        
+        if (c._tooltipElem) {
+            c._tooltipElem.emptyForce();
+            c._tooltipElem = null;
+        }
+
+        
+        if (c.zoom) {
+            c.zoomCanvas = new $.jqplot.GenericCanvas();
+            this.eventCanvas._elem.before(c.zoomCanvas.createElement(this._gridPadding, 'jqplot-zoom-canvas', this._plotDimensions, this));
+            c.zoomCanvas.setContext();
+        }
+
+        var elem = document.createElement('div');
+        c._tooltipElem = $(elem);
+        elem = null;
+        c._tooltipElem.addClass('jqplot-cursor-tooltip');
+        c._tooltipElem.css({position:'absolute', display:'none'});
+        
+        
+        if (c.zoomCanvas) {
+            c.zoomCanvas._elem.before(c._tooltipElem);
+        }
+
+        else {
+            this.eventCanvas._elem.before(c._tooltipElem);
+        }
+
+        if (c.showVerticalLine || c.showHorizontalLine) {
+            c.cursorCanvas = new $.jqplot.GenericCanvas();
+            this.eventCanvas._elem.before(c.cursorCanvas.createElement(this._gridPadding, 'jqplot-cursor-canvas', this._plotDimensions, this));
+            c.cursorCanvas.setContext();
+        }
+
+        // if we are showing the positions in unit coordinates, and no axes groups
+        // were specified, create a default set.
+        if (c.showTooltipUnitPosition){
+            if (c.tooltipAxisGroups.length === 0) {
+                var series = this.series;
+                var s;
+                var temp = [];
+                for (var i=0; i<series.length; i++) {
+                    s = series[i];
+                    var ax = s.xaxis+','+s.yaxis;
+                    if ($.inArray(ax, temp) == -1) {
+                        temp.push(ax);
+                    }
+                }
+                for (var i=0; i<temp.length; i++) {
+                    c.tooltipAxisGroups.push(temp[i].split(','));
+                }
+            }
+        }
+    };
+    
+    // Group: methods
+    //
+    // method: $.jqplot.Cursor.zoomProxy
+    // links targetPlot to controllerPlot so that plot zooming of
+    // targetPlot will be controlled by zooming on the controllerPlot.
+    // controllerPlot will not actually zoom, but acts as an
+    // overview plot.  Note, the zoom options must be set to true for
+    // zoomProxy to work.
+    $.jqplot.Cursor.zoomProxy = function(targetPlot, controllerPlot) {
+        var tc = targetPlot.plugins.cursor;
+        var cc = controllerPlot.plugins.cursor;
+        tc.zoomTarget = true;
+        tc.zoom = true;
+        tc.style = 'auto';
+        tc.dblClickReset = false;
+        cc.zoom = true;
+        cc.zoomProxy = true;
+              
+        controllerPlot.target.bind('jqplotZoom', plotZoom);
+        controllerPlot.target.bind('jqplotResetZoom', plotReset);
+
+        function plotZoom(ev, gridpos, datapos, plot, cursor) {
+            tc.doZoom(gridpos, datapos, targetPlot, cursor);
+        } 
+
+        function plotReset(ev, plot, cursor) {
+            targetPlot.resetZoom();
+        }
+    };
+    
+    $.jqplot.Cursor.prototype.resetZoom = function(plot, cursor) {
+        var axes = plot.axes;
+        var cax = cursor._zoom.axes;
+        if (!plot.plugins.cursor.zoomProxy && cursor._zoom.isZoomed) {
+            for (var ax in axes) {
+                // axes[ax]._ticks = [];
+                // axes[ax].min = cax[ax].min;
+                // axes[ax].max = cax[ax].max;
+                // axes[ax].numberTicks = cax[ax].numberTicks; 
+                // axes[ax].tickInterval = cax[ax].tickInterval;
+                // // for date axes
+                // axes[ax].daTickInterval = cax[ax].daTickInterval;
+                axes[ax].reset();
+                axes[ax]._ticks = [];
+                // fake out tick creation algorithm to make sure original auto
+                // computed format string is used if _overrideFormatString is true
+                axes[ax]._autoFormatString = cax[ax].tickFormatString;
+            }
+            plot.redraw();
+            cursor._zoom.isZoomed = false;
+        }
+        else {
+            var ctx = cursor.zoomCanvas._ctx;
+            ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+            ctx = null;
+        }
+        plot.target.trigger('jqplotResetZoom', [plot, cursor]);
+    };
+    
+    $.jqplot.Cursor.resetZoom = function(plot) {
+        plot.resetZoom();
+    };
+    
+    $.jqplot.Cursor.prototype.doZoom = function (gridpos, datapos, plot, cursor) {
+        var c = cursor;
+        var axes = plot.axes;
+        var zaxes = c._zoom.axes;
+        var start = zaxes.start;
+        var end = zaxes.end;
+        var min, max, dp, span,
+            newmin, newmax, curax, _numberTicks, ret;
+        var ctx = plot.plugins.cursor.zoomCanvas._ctx;
+        // don't zoom if zoom area is too small (in pixels)
+        if ((c.constrainZoomTo == 'none' && Math.abs(gridpos.x - c._zoom.start[0]) > 6 && Math.abs(gridpos.y - c._zoom.start[1]) > 6) || (c.constrainZoomTo == 'x' && Math.abs(gridpos.x - c._zoom.start[0]) > 6) ||  (c.constrainZoomTo == 'y' && Math.abs(gridpos.y - c._zoom.start[1]) > 6)) {
+            if (!plot.plugins.cursor.zoomProxy) {
+                for (var ax in datapos) {
+                    // make a copy of the original axes to revert back.
+                    if (c._zoom.axes[ax] == undefined) {
+                        c._zoom.axes[ax] = {};
+                        c._zoom.axes[ax].numberTicks = axes[ax].numberTicks;
+                        c._zoom.axes[ax].tickInterval = axes[ax].tickInterval;
+                        // for date axes...
+                        c._zoom.axes[ax].daTickInterval = axes[ax].daTickInterval;
+                        c._zoom.axes[ax].min = axes[ax].min;
+                        c._zoom.axes[ax].max = axes[ax].max;
+                        c._zoom.axes[ax].tickFormatString = (axes[ax].tickOptions != null) ? axes[ax].tickOptions.formatString :  '';
+                    }
+
+
+                    if ((c.constrainZoomTo == 'none') || (c.constrainZoomTo == 'x' && ax.charAt(0) == 'x') || (c.constrainZoomTo == 'y' && ax.charAt(0) == 'y')) {   
+                        dp = datapos[ax];
+                        if (dp != null) {           
+                            if (dp > start[ax]) { 
+                                newmin = start[ax];
+                                newmax = dp;
+                            }
+                            else {
+                                span = start[ax] - dp;
+                                newmin = dp;
+                                newmax = start[ax];
+                            }
+
+                            curax = axes[ax];
+
+                            _numberTicks = null;
+
+                            // if aligning this axis, use number of ticks from previous axis.
+                            // Do I need to reset somehow if alignTicks is changed and then graph is replotted??
+                            if (curax.alignTicks) {
+                                if (curax.name === 'x2axis' && plot.axes.xaxis.show) {
+                                    _numberTicks = plot.axes.xaxis.numberTicks;
+                                }
+                                else if (curax.name.charAt(0) === 'y' && curax.name !== 'yaxis' && curax.name !== 'yMidAxis' && plot.axes.yaxis.show) {
+                                    _numberTicks = plot.axes.yaxis.numberTicks;
+                                }
+                            }
+                            
+                            if (this.looseZoom && (axes[ax].renderer.constructor === $.jqplot.LinearAxisRenderer || axes[ax].renderer.constructor === $.jqplot.LogAxisRenderer )) { //} || axes[ax].renderer.constructor === $.jqplot.DateAxisRenderer)) {
+
+                                ret = $.jqplot.LinearTickGenerator(newmin, newmax, curax._scalefact, _numberTicks);
+
+                                // if new minimum is less than "true" minimum of axis display, adjust it
+                                if (axes[ax].tickInset && ret[0] < axes[ax].min + axes[ax].tickInset * axes[ax].tickInterval) {
+                                    ret[0] += ret[4];
+                                    ret[2] -= 1;
+                                }
+
+                                // if new maximum is greater than "true" max of axis display, adjust it
+                                if (axes[ax].tickInset && ret[1] > axes[ax].max - axes[ax].tickInset * axes[ax].tickInterval) {
+                                    ret[1] -= ret[4];
+                                    ret[2] -= 1;
+                                }
+
+                                // for log axes, don't fall below current minimum, this will look bad and can't have 0 in range anyway.
+                                if (axes[ax].renderer.constructor === $.jqplot.LogAxisRenderer && ret[0] < axes[ax].min) {
+                                    // remove a tick and shift min up
+                                    ret[0] += ret[4];
+                                    ret[2] -= 1;
+                                }
+
+                                axes[ax].min = ret[0];
+                                axes[ax].max = ret[1];
+                                axes[ax]._autoFormatString = ret[3];
+                                axes[ax].numberTicks = ret[2];
+                                axes[ax].tickInterval = ret[4];
+                                // for date axes...
+                                axes[ax].daTickInterval = [ret[4]/1000, 'seconds'];
+                            }
+                            else {
+                                axes[ax].min = newmin;
+                                axes[ax].max = newmax;
+                                axes[ax].tickInterval = null;
+                                axes[ax].numberTicks = null;
+                                // for date axes...
+                                axes[ax].daTickInterval = null;
+                            }
+
+                            axes[ax]._ticks = [];
+                        }
+                    }
+                            
+                    // if ((c.constrainZoomTo == 'x' && ax.charAt(0) == 'y' && c.autoscaleConstraint) || (c.constrainZoomTo == 'y' && ax.charAt(0) == 'x' && c.autoscaleConstraint)) {
+                    //     dp = datapos[ax];
+                    //     if (dp != null) {
+                    //         axes[ax].max == null;
+                    //         axes[ax].min = null;
+                    //     }
+                    // }
+                }
+                ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+                plot.redraw();
+                c._zoom.isZoomed = true;
+                ctx = null;
+            }
+            plot.target.trigger('jqplotZoom', [gridpos, datapos, plot, cursor]);
+        }
+    };
+    
+    $.jqplot.preInitHooks.push($.jqplot.Cursor.init);
+    $.jqplot.postDrawHooks.push($.jqplot.Cursor.postDraw);
+    
+    function updateTooltip(gridpos, datapos, plot) {
+        var c = plot.plugins.cursor;
+        var s = '';
+        var addbr = false;
+        if (c.showTooltipGridPosition) {
+            s = gridpos.x+', '+gridpos.y;
+            addbr = true;
+        }
+        if (c.showTooltipUnitPosition) {
+            var g;
+            for (var i=0; i<c.tooltipAxisGroups.length; i++) {
+                g = c.tooltipAxisGroups[i];
+                if (addbr) {
+                    s += '<br />';
+                }
+                if (c.useAxesFormatters) {
+                    for (var j=0; j<g.length; j++) {
+                        if (j) {
+                            s += ', ';
+                        }
+                        var af = plot.axes[g[j]]._ticks[0].formatter;
+                        var afstr = plot.axes[g[j]]._ticks[0].formatString;
+                        s += af(afstr, datapos[g[j]]);
+                    }
+                }
+                else {
+                    s += $.jqplot.sprintf(c.tooltipFormatString, datapos[g[0]], datapos[g[1]]);
+                }
+                addbr = true;
+            }
+        }
+        
+        if (c.showTooltipDataPosition) {
+            var series = plot.series; 
+            var ret = getIntersectingPoints(plot, gridpos.x, gridpos.y);
+            var addbr = false;
+        
+            for (var i = 0; i< series.length; i++) {
+                if (series[i].show) {
+                    var idx = series[i].index;
+                    var label = series[i].label.toString();
+                    var cellid = $.inArray(idx, ret.indices);
+                    var sx = undefined;
+                    var sy = undefined;
+                    if (cellid != -1) {
+                        var data = ret.data[cellid].data;
+                        if (c.useAxesFormatters) {
+                            var xf = series[i]._xaxis._ticks[0].formatter;
+                            var yf = series[i]._yaxis._ticks[0].formatter;
+                            var xfstr = series[i]._xaxis._ticks[0].formatString;
+                            var yfstr = series[i]._yaxis._ticks[0].formatString;
+                            sx = xf(xfstr, data[0]);
+                            sy = yf(yfstr, data[1]);
+                        }
+                        else {
+                            sx = data[0];
+                            sy = data[1];
+                        }
+                        if (addbr) {
+                            s += '<br />';
+                        }
+                        s += $.jqplot.sprintf(c.tooltipFormatString, label, sx, sy);
+                        addbr = true;
+                    }
+                }
+            }
+            
+        }
+        c._tooltipElem.html(s);
+    }
+    
+    function moveLine(gridpos, plot) {
+        var c = plot.plugins.cursor;
+        var ctx = c.cursorCanvas._ctx;
+        ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+        if (c.showVerticalLine) {
+            c.shapeRenderer.draw(ctx, [[gridpos.x, 0], [gridpos.x, ctx.canvas.height]]);
+        }
+        if (c.showHorizontalLine) {
+            c.shapeRenderer.draw(ctx, [[0, gridpos.y], [ctx.canvas.width, gridpos.y]]);
+        }
+        var ret = getIntersectingPoints(plot, gridpos.x, gridpos.y);
+        if (c.showCursorLegend) {
+            var cells = $(plot.targetId + ' td.jqplot-cursor-legend-label');
+            for (var i=0; i<cells.length; i++) {
+                var idx = $(cells[i]).data('seriesIndex');
+                var series = plot.series[idx];
+                var label = series.label.toString();
+                var cellid = $.inArray(idx, ret.indices);
+                var sx = undefined;
+                var sy = undefined;
+                if (cellid != -1) {
+                    var data = ret.data[cellid].data;
+                    if (c.useAxesFormatters) {
+                        var xf = series._xaxis._ticks[0].formatter;
+                        var yf = series._yaxis._ticks[0].formatter;
+                        var xfstr = series._xaxis._ticks[0].formatString;
+                        var yfstr = series._yaxis._ticks[0].formatString;
+                        sx = xf(xfstr, data[0]);
+                        sy = yf(yfstr, data[1]);
+                    }
+                    else {
+                        sx = data[0];
+                        sy = data[1];
+                    }
+                }
+                if (plot.legend.escapeHtml) {
+                    $(cells[i]).text($.jqplot.sprintf(c.cursorLegendFormatString, label, sx, sy));
+                }
+                else {
+                    $(cells[i]).html($.jqplot.sprintf(c.cursorLegendFormatString, label, sx, sy));
+                }
+            }        
+        }
+        ctx = null;
+    }
+        
+    function getIntersectingPoints(plot, x, y) {
+        var ret = {indices:[], data:[]};
+        var s, i, d0, d, j, r, p;
+        var threshold;
+        var c = plot.plugins.cursor;
+        for (var i=0; i<plot.series.length; i++) {
+            s = plot.series[i];
+            r = s.renderer;
+            if (s.show) {
+                threshold = c.intersectionThreshold;
+                if (s.showMarker) {
+                    threshold += s.markerRenderer.size/2;
+                }
+                for (var j=0; j<s.gridData.length; j++) {
+                    p = s.gridData[j];
+                    // check vertical line
+                    if (c.showVerticalLine) {
+                        if (Math.abs(x-p[0]) <= threshold) {
+                            ret.indices.push(i);
+                            ret.data.push({seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]});
+                        }
+                    }
+                } 
+            }
+        }
+        return ret;
+    }
+    
+    function moveTooltip(gridpos, plot) {
+        var c = plot.plugins.cursor;  
+        var elem = c._tooltipElem;
+        switch (c.tooltipLocation) {
+            case 'nw':
+                var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
+                var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
+                break;
+            case 'n':
+                var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
+                var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
+                break;
+            case 'ne':
+                var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
+                var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
+                break;
+            case 'e':
+                var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
+                var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
+                break;
+            case 'se':
+                var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
+                var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
+                break;
+            case 's':
+                var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
+                var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
+                break;
+            case 'sw':
+                var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
+                var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
+                break;
+            case 'w':
+                var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
+                var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
+                break;
+            default:
+                var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
+                var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
+                break;
+        }
+            
+        elem.css('left', x);
+        elem.css('top', y);
+        elem = null;
+    }
+    
+    function positionTooltip(plot) { 
+        // fake a grid for positioning
+        var grid = plot._gridPadding; 
+        var c = plot.plugins.cursor;
+        var elem = c._tooltipElem;  
+        switch (c.tooltipLocation) {
+            case 'nw':
+                var a = grid.left + c.tooltipOffset;
+                var b = grid.top + c.tooltipOffset;
+                elem.css('left', a);
+                elem.css('top', b);
+                break;
+            case 'n':
+                var a = (grid.left + (plot._plotDimensions.width - grid.right))/2 - elem.outerWidth(true)/2;
+                var b = grid.top + c.tooltipOffset;
+                elem.css('left', a);
+                elem.css('top', b);
+                break;
+            case 'ne':
+                var a = grid.right + c.tooltipOffset;
+                var b = grid.top + c.tooltipOffset;
+                elem.css({right:a, top:b});
+                break;
+            case 'e':
+                var a = grid.right + c.tooltipOffset;
+                var b = (grid.top + (plot._plotDimensions.height - grid.bottom))/2 - elem.outerHeight(true)/2;
+                elem.css({right:a, top:b});
+                break;
+            case 'se':
+                var a = grid.right + c.tooltipOffset;
+                var b = grid.bottom + c.tooltipOffset;
+                elem.css({right:a, bottom:b});
+                break;
+            case 's':
+                var a = (grid.left + (plot._plotDimensions.width - grid.right))/2 - elem.outerWidth(true)/2;
+                var b = grid.bottom + c.tooltipOffset;
+                elem.css({left:a, bottom:b});
+                break;
+            case 'sw':
+                var a = grid.left + c.tooltipOffset;
+                var b = grid.bottom + c.tooltipOffset;
+                elem.css({left:a, bottom:b});
+                break;
+            case 'w':
+                var a = grid.left + c.tooltipOffset;
+                var b = (grid.top + (plot._plotDimensions.height - grid.bottom))/2 - elem.outerHeight(true)/2;
+                elem.css({left:a, top:b});
+                break;
+            default:  // same as 'se'
+                var a = grid.right - c.tooltipOffset;
+                var b = grid.bottom + c.tooltipOffset;
+                elem.css({right:a, bottom:b});
+                break;
+        }
+        elem = null;
+    }
+    
+    function handleClick (ev, gridpos, datapos, neighbor, plot) {
+        ev.preventDefault();
+        ev.stopImmediatePropagation();
+        var c = plot.plugins.cursor;
+        if (c.clickReset) {
+            c.resetZoom(plot, c);
+        }
+        var sel = window.getSelection;
+        if (document.selection && document.selection.empty)
+        {
+            document.selection.empty();
+        }
+        else if (sel && !sel().isCollapsed) {
+            sel().collapse();
+        }
+        return false;
+    }
+    
+    function handleDblClick (ev, gridpos, datapos, neighbor, plot) {
+        ev.preventDefault();
+        ev.stopImmediatePropagation();
+        var c = plot.plugins.cursor;
+        if (c.dblClickReset) {
+            c.resetZoom(plot, c);
+        }
+        var sel = window.getSelection;
+        if (document.selection && document.selection.empty)
+        {
+            document.selection.empty();
+        }
+        else if (sel && !sel().isCollapsed) {
+            sel().collapse();
+        }
+        return false;
+    }
+    
+    function handleMouseLeave(ev, gridpos, datapos, neighbor, plot) {
+        var c = plot.plugins.cursor;
+        c.onGrid = false;
+        if (c.show) {
+            $(ev.target).css('cursor', c.previousCursor);
+            if (c.showTooltip && !(c._zoom.zooming && c.showTooltipOutsideZoom && !c.constrainOutsideZoom)) {
+                c._tooltipElem.empty();
+                c._tooltipElem.hide();
+            }
+            if (c.zoom) {
+                c._zoom.gridpos = gridpos;
+                c._zoom.datapos = datapos;
+            }
+            if (c.showVerticalLine || c.showHorizontalLine) {
+                var ctx = c.cursorCanvas._ctx;
+                ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+                ctx = null;
+            }
+            if (c.showCursorLegend) {
+                var cells = $(plot.targetId + ' td.jqplot-cursor-legend-label');
+                for (var i=0; i<cells.length; i++) {
+                    var idx = $(cells[i]).data('seriesIndex');
+                    var series = plot.series[idx];
+                    var label = series.label.toString();
+                    if (plot.legend.escapeHtml) {
+                        $(cells[i]).text($.jqplot.sprintf(c.cursorLegendFormatString, label, undefined, undefined));
+                    }
+                    else {
+                        $(cells[i]).html($.jqplot.sprintf(c.cursorLegendFormatString, label, undefined, undefined));
+                    }
+                
+                }        
+            }
+        }
+    }
+    
+    function handleMouseEnter(ev, gridpos, datapos, neighbor, plot) {
+        var c = plot.plugins.cursor;
+        c.onGrid = true;
+        if (c.show) {
+            c.previousCursor = ev.target.style.cursor;
+            ev.target.style.cursor = c.style;
+            if (c.showTooltip) {
+                updateTooltip(gridpos, datapos, plot);
+                if (c.followMouse) {
+                    moveTooltip(gridpos, plot);
+                }
+                else {
+                    positionTooltip(plot);
+                }
+                c._tooltipElem.show();
+            }
+            if (c.showVerticalLine || c.showHorizontalLine) {
+                moveLine(gridpos, plot);
+            }
+        }
+
+    }    
+    
+    function handleMouseMove(ev, gridpos, datapos, neighbor, plot) {
+        var c = plot.plugins.cursor;
+        if (c.show) {
+            if (c.showTooltip) {
+                updateTooltip(gridpos, datapos, plot);
+                if (c.followMouse) {
+                    moveTooltip(gridpos, plot);
+                }
+            }
+            if (c.showVerticalLine || c.showHorizontalLine) {
+                moveLine(gridpos, plot);
+            }
+        }
+    }
+            
+    function getEventPosition(ev) {
+        var plot = ev.data.plot;
+        var go = plot.eventCanvas._elem.offset();
+        var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top};
+        //////
+        // TO DO: handle yMidAxis
+        //////
+        var dataPos = {xaxis:null, yaxis:null, x2axis:null, y2axis:null, y3axis:null, y4axis:null, y5axis:null, y6axis:null, y7axis:null, y8axis:null, y9axis:null, yMidAxis:null};
+        var an = ['xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
+        var ax = plot.axes;
+        var n, axis;
+        for (n=11; n>0; n--) {
+            axis = an[n-1];
+            if (ax[axis].show) {
+                dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]);
+            }
+        }
+
+        return {offsets:go, gridPos:gridPos, dataPos:dataPos};
+    }    
+    
+    function handleZoomMove(ev) {
+        var plot = ev.data.plot;
+        var c = plot.plugins.cursor;
+        // don't do anything if not on grid.
+        if (c.show && c.zoom && c._zoom.started && !c.zoomTarget) {
+            ev.preventDefault();
+            var ctx = c.zoomCanvas._ctx;
+            var positions = getEventPosition(ev);
+            var gridpos = positions.gridPos;
+            var datapos = positions.dataPos;
+            c._zoom.gridpos = gridpos;
+            c._zoom.datapos = datapos;
+            c._zoom.zooming = true;
+            var xpos = gridpos.x;
+            var ypos = gridpos.y;
+            var height = ctx.canvas.height;
+            var width = ctx.canvas.width;
+            if (c.showTooltip && !c.onGrid && c.showTooltipOutsideZoom) {
+                updateTooltip(gridpos, datapos, plot);
+                if (c.followMouse) {
+                    moveTooltip(gridpos, plot);
+                }
+            }
+            if (c.constrainZoomTo == 'x') {
+                c._zoom.end = [xpos, height];
+            }
+            else if (c.constrainZoomTo == 'y') {
+                c._zoom.end = [width, ypos];
+            }
+            else {
+                c._zoom.end = [xpos, ypos];
+            }
+            var sel = window.getSelection;
+            if (document.selection && document.selection.empty)
+            {
+                document.selection.empty();
+            }
+            else if (sel && !sel().isCollapsed) {
+                sel().collapse();
+            }
+            drawZoomBox.call(c);
+            ctx = null;
+        }
+    }
+    
+    function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
+        var c = plot.plugins.cursor;
+        if(plot.plugins.mobile){
+            $(document).one('vmouseup.jqplot_cursor', {plot:plot}, handleMouseUp);
+        } else {
+            $(document).one('mouseup.jqplot_cursor', {plot:plot}, handleMouseUp);
+        }
+        var axes = plot.axes;
+        if (document.onselectstart != undefined) {
+            c._oldHandlers.onselectstart = document.onselectstart;
+            document.onselectstart = function () { return false; };
+        }
+        if (document.ondrag != undefined) {
+            c._oldHandlers.ondrag = document.ondrag;
+            document.ondrag = function () { return false; };
+        }
+        if (document.onmousedown != undefined) {
+            c._oldHandlers.onmousedown = document.onmousedown;
+            document.onmousedown = function () { return false; };
+        }
+        if (c.zoom) {
+            if (!c.zoomProxy) {
+                var ctx = c.zoomCanvas._ctx;
+                ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+                ctx = null;
+            }
+            if (c.constrainZoomTo == 'x') {
+                c._zoom.start = [gridpos.x, 0];
+            }
+            else if (c.constrainZoomTo == 'y') {
+                c._zoom.start = [0, gridpos.y];
+            }
+            else {
+                c._zoom.start = [gridpos.x, gridpos.y];
+            }
+            c._zoom.started = true;
+            for (var ax in datapos) {
+                // get zoom starting position.
+                c._zoom.axes.start[ax] = datapos[ax];
+            }  
+           if(plot.plugins.mobile){
+                $(document).bind('vmousemove.jqplotCursor', {plot:plot}, handleZoomMove);              
+            } else {
+                $(document).bind('mousemove.jqplotCursor', {plot:plot}, handleZoomMove);              
+            }
+
+        }
+    }
+    
+    function handleMouseUp(ev) {
+        var plot = ev.data.plot;
+        var c = plot.plugins.cursor;
+        if (c.zoom && c._zoom.zooming && !c.zoomTarget) {
+            var xpos = c._zoom.gridpos.x;
+            var ypos = c._zoom.gridpos.y;
+            var datapos = c._zoom.datapos;
+            var height = c.zoomCanvas._ctx.canvas.height;
+            var width = c.zoomCanvas._ctx.canvas.width;
+            var axes = plot.axes;
+            
+            if (c.constrainOutsideZoom && !c.onGrid) {
+                if (xpos < 0) { xpos = 0; }
+                else if (xpos > width) { xpos = width; }
+                if (ypos < 0) { ypos = 0; }
+                else if (ypos > height) { ypos = height; }
+                
+                for (var axis in datapos) {
+                    if (datapos[axis]) {
+                        if (axis.charAt(0) == 'x') {
+                            datapos[axis] = axes[axis].series_p2u(xpos);
+                        }
+                        else {
+                            datapos[axis] = axes[axis].series_p2u(ypos);
+                        }
+                    }
+                }
+            }
+            
+            if (c.constrainZoomTo == 'x') {
+                ypos = height;
+            }
+            else if (c.constrainZoomTo == 'y') {
+                xpos = width;
+            }
+            c._zoom.end = [xpos, ypos];
+            c._zoom.gridpos = {x:xpos, y:ypos};
+            
+            c.doZoom(c._zoom.gridpos, datapos, plot, c);
+        }
+        c._zoom.started = false;
+        c._zoom.zooming = false;
+        
+        $(document).unbind('mousemove.jqplotCursor', handleZoomMove);
+        
+        if (document.onselectstart != undefined && c._oldHandlers.onselectstart != null){
+            document.onselectstart = c._oldHandlers.onselectstart;
+            c._oldHandlers.onselectstart = null;
+        }
+        if (document.ondrag != undefined && c._oldHandlers.ondrag != null){
+            document.ondrag = c._oldHandlers.ondrag;
+            c._oldHandlers.ondrag = null;
+        }
+        if (document.onmousedown != undefined && c._oldHandlers.onmousedown != null){
+            document.onmousedown = c._oldHandlers.onmousedown;
+            c._oldHandlers.onmousedown = null;
+        }
+
+    }
+    
+    function drawZoomBox() {
+        var start = this._zoom.start;
+        var end = this._zoom.end;
+        var ctx = this.zoomCanvas._ctx;
+        var l, t, h, w;
+        if (end[0] > start[0]) {
+            l = start[0];
+            w = end[0] - start[0];
+        }
+        else {
+            l = end[0];
+            w = start[0] - end[0];
+        }
+        if (end[1] > start[1]) {
+            t = start[1];
+            h = end[1] - start[1];
+        }
+        else {
+            t = end[1];
+            h = start[1] - end[1];
+        }
+        ctx.fillStyle = 'rgba(0,0,0,0.2)';
+        ctx.strokeStyle = '#999999';
+        ctx.lineWidth = 1.0;
+        ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
+        ctx.fillRect(0,0,ctx.canvas.width, ctx.canvas.height);
+        ctx.clearRect(l, t, w, h);
+        // IE won't show transparent fill rect, so stroke a rect also.
+        ctx.strokeRect(l,t,w,h);
+        ctx = null;
+    }
+    
+    $.jqplot.CursorLegendRenderer = function(options) {
+        $.jqplot.TableLegendRenderer.call(this, options);
+        this.formatString = '%s';
+    };
+    
+    $.jqplot.CursorLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
+    $.jqplot.CursorLegendRenderer.prototype.constructor = $.jqplot.CursorLegendRenderer;
+    
+    // called in context of a Legend
+    $.jqplot.CursorLegendRenderer.prototype.draw = function() {
+        if (this._elem) {
+            this._elem.emptyForce();
+            this._elem = null;
+        }
+        if (this.show) {
+            var series = this._series, s;
+            // make a table.  one line label per row.
+            var elem = document.createElement('table');
+            this._elem = $(elem);
+            elem = null;
+            this._elem.addClass('jqplot-legend jqplot-cursor-legend');
+            this._elem.css('position', 'absolute');
+        
+            var pad = false;
+            for (var i = 0; i< series.length; i++) {
+                s = series[i];
+                if (s.show && s.showLabel) {
+                    var lt = $.jqplot.sprintf(this.formatString, s.label.toString());
+                    if (lt) {
+                        var color = s.color;
+                        if (s._stack && !s.fill) {
+                            color = '';
+                        }
+                        addrow.call(this, lt, color, pad, i);
+                        pad = true;
+                    }
+                    // let plugins add more rows to legend.  Used by trend line plugin.
+                    for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) {
+                        var item = $.jqplot.addLegendRowHooks[j].call(this, s);
+                        if (item) {
+                            addrow.call(this, item.label, item.color, pad);
+                            pad = true;
+                        } 
+                    }
+                }
+            }
+            series = s = null;
+            delete series;
+            delete s;
+        }
+        
+        function addrow(label, color, pad, idx) {
+            var rs = (pad) ? this.rowSpacing : '0';
+            var tr = $('<tr class="jqplot-legend jqplot-cursor-legend"></tr>').appendTo(this._elem);
+            tr.data('seriesIndex', idx);
+            $('<td class="jqplot-legend jqplot-cursor-legend-swatch" style="padding-top:'+rs+';">'+
+                '<div style="border:1px solid #cccccc;padding:0.2em;">'+
+                '<div class="jqplot-cursor-legend-swatch" style="background-color:'+color+';"></div>'+
+                '</div></td>').appendTo(tr);
+            var td = $('<td class="jqplot-legend jqplot-cursor-legend-label" style="vertical-align:middle;padding-top:'+rs+';"></td>');
+            td.appendTo(tr);
+            td.data('seriesIndex', idx);
+            if (this.escapeHtml) {
+                td.text(label);
+            }
+            else {
+                td.html(label);
+            }
+            tr = null;
+            td = null;
+        }
+        return this._elem;
+    };
+    
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.dateAxisRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.dateAxisRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.dateAxisRenderer.js	(revision 2119)
@@ -0,0 +1,741 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {  
+    /**
+     * Class: $.jqplot.DateAxisRenderer
+     * A plugin for a jqPlot to render an axis as a series of date values.
+     * This renderer has no options beyond those supplied by the <Axis> class.
+     * It supplies its own tick formatter, so the tickOptions.formatter option
+     * should not be overridden.
+     * 
+     * Thanks to Ken Synder for his enhanced Date instance methods which are
+     * included with this code <http://kendsnyder.com/sandbox/date/>.
+     * 
+     * To use this renderer, include the plugin in your source
+     * > <script type="text/javascript" language="javascript" src="plugins/jqplot.dateAxisRenderer.js"></script>
+     * 
+     * and supply the appropriate options to your plot
+     * 
+     * > {axes:{xaxis:{renderer:$.jqplot.DateAxisRenderer}}}
+     * 
+     * Dates can be passed into the axis in almost any recognizable value and 
+     * will be parsed.  They will be rendered on the axis in the format
+     * specified by tickOptions.formatString.  e.g. tickOptions.formatString = '%Y-%m-%d'.
+     * 
+     * Accecptable format codes 
+     * are:
+     * 
+     * > Code    Result                  Description
+     * >             == Years ==
+     * > %Y      2008                Four-digit year
+     * > %y      08                  Two-digit year
+     * >             == Months ==
+     * > %m      09                  Two-digit month
+     * > %#m     9                   One or two-digit month
+     * > %B      September           Full month name
+     * > %b      Sep                 Abbreviated month name
+     * >             == Days ==
+     * > %d      05                  Two-digit day of month
+     * > %#d     5                   One or two-digit day of month
+     * > %e      5                   One or two-digit day of month
+     * > %A      Sunday              Full name of the day of the week
+     * > %a      Sun                 Abbreviated name of the day of the week
+     * > %w      0                   Number of the day of the week (0 = Sunday, 6 = Saturday)
+     * > %o      th                  The ordinal suffix string following the day of the month
+     * >             == Hours ==
+     * > %H      23                  Hours in 24-hour format (two digits)
+     * > %#H     3                   Hours in 24-hour integer format (one or two digits)
+     * > %I      11                  Hours in 12-hour format (two digits)
+     * > %#I     3                   Hours in 12-hour integer format (one or two digits)
+     * > %p      PM                  AM or PM
+     * >             == Minutes ==
+     * > %M      09                  Minutes (two digits)
+     * > %#M     9                   Minutes (one or two digits)
+     * >             == Seconds ==
+     * > %S      02                  Seconds (two digits)
+     * > %#S     2                   Seconds (one or two digits)
+     * > %s      1206567625723       Unix timestamp (Seconds past 1970-01-01 00:00:00)
+     * >             == Milliseconds ==
+     * > %N      008                 Milliseconds (three digits)
+     * > %#N     8                   Milliseconds (one to three digits)
+     * >             == Timezone ==
+     * > %O      360                 difference in minutes between local time and GMT
+     * > %Z      Mountain Standard Time  Name of timezone as reported by browser
+     * > %G      -06:00              Hours and minutes between GMT
+     * >             == Shortcuts ==
+     * > %F      2008-03-26          %Y-%m-%d
+     * > %T      05:06:30            %H:%M:%S
+     * > %X      05:06:30            %H:%M:%S
+     * > %x      03/26/08            %m/%d/%y
+     * > %D      03/26/08            %m/%d/%y
+     * > %#c     Wed Mar 26 15:31:00 2008  %a %b %e %H:%M:%S %Y
+     * > %v      3-Sep-2008          %e-%b-%Y
+     * > %R      15:31               %H:%M
+     * > %r      3:31:00 PM          %I:%M:%S %p
+     * >             == Characters ==
+     * > %n      \n                  Newline
+     * > %t      \t                  Tab
+     * > %%      %                   Percent Symbol 
+     */
+    $.jqplot.DateAxisRenderer = function() {
+        $.jqplot.LinearAxisRenderer.call(this);
+        this.date = new $.jsDate();
+    };
+
+    var second = 1000;
+    var minute = 60 * second;
+    var hour = 60 * minute;
+    var day = 24 * hour;
+    var week = 7 * day;
+
+    // these are less definitive
+    var month = 30.4368499 * day;
+    var year = 365.242199 * day;
+
+    var daysInMonths = [31,28,31,30,31,30,31,30,31,30,31,30];
+    // array of consistent nice intervals.  Longer intervals
+    // will depend on days in month, days in year, etc.
+    var niceFormatStrings = ['%M:%S.%#N', '%M:%S.%#N', '%M:%S.%#N', '%M:%S', '%M:%S', '%M:%S', '%M:%S', '%H:%M:%S', '%H:%M:%S', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%a %H:%M', '%a %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%v', '%v', '%v', '%v', '%v', '%v', '%v'];
+    var niceIntervals = [0.1*second, 0.2*second, 0.5*second, second, 2*second, 5*second, 10*second, 15*second, 30*second, minute, 2*minute, 5*minute, 10*minute, 15*minute, 30*minute, hour, 2*hour, 4*hour, 6*hour, 8*hour, 12*hour, day, 2*day, 3*day, 4*day, 5*day, week, 2*week];
+
+    var niceMonthlyIntervals = [];
+
+    function bestDateInterval(min, max, titarget) {
+        // iterate through niceIntervals to find one closest to titarget
+        var badness = Number.MAX_VALUE;
+        var temp, bestTi, bestfmt;
+        for (var i=0, l=niceIntervals.length; i < l; i++) {
+            temp = Math.abs(titarget - niceIntervals[i]);
+            if (temp < badness) {
+                badness = temp;
+                bestTi = niceIntervals[i];
+                bestfmt = niceFormatStrings[i];
+            }
+        }
+
+        return [bestTi, bestfmt];
+    }
+    
+    $.jqplot.DateAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+    $.jqplot.DateAxisRenderer.prototype.constructor = $.jqplot.DateAxisRenderer;
+    
+    $.jqplot.DateTickFormatter = function(format, val) {
+        if (!format) {
+            format = '%Y/%m/%d';
+        }
+        return $.jsDate.strftime(val, format);
+    };
+    
+    $.jqplot.DateAxisRenderer.prototype.init = function(options){
+        // prop: tickRenderer
+        // A class of a rendering engine for creating the ticks labels displayed on the plot, 
+        // See <$.jqplot.AxisTickRenderer>.
+        // this.tickRenderer = $.jqplot.AxisTickRenderer;
+        // this.labelRenderer = $.jqplot.AxisLabelRenderer;
+        this.tickOptions.formatter = $.jqplot.DateTickFormatter;
+        // prop: tickInset
+        // Controls the amount to inset the first and last ticks from 
+        // the edges of the grid, in multiples of the tick interval.
+        // 0 is no inset, 0.5 is one half a tick interval, 1 is a full
+        // tick interval, etc.
+        this.tickInset = 0;
+        // prop: drawBaseline
+        // True to draw the axis baseline.
+        this.drawBaseline = true;
+        // prop: baselineWidth
+        // width of the baseline in pixels.
+        this.baselineWidth = null;
+        // prop: baselineColor
+        // CSS color spec for the baseline.
+        this.baselineColor = null;
+        this.daTickInterval = null;
+        this._daTickInterval = null;
+        
+        $.extend(true, this, options);
+        
+        var db = this._dataBounds,
+            stats, 
+            sum,
+            s,
+            d,
+            pd,
+            sd,
+            intv;
+        
+        // Go through all the series attached to this axis and find
+        // the min/max bounds for this axis.
+        for (var i=0; i<this._series.length; i++) {
+            stats = {intervals:[], frequencies:{}, sortedIntervals:[], min:null, max:null, mean:null};
+            sum = 0;
+            s = this._series[i];
+            d = s.data;
+            pd = s._plotData;
+            sd = s._stackData;
+            intv = 0;
+            
+            for (var j=0; j<d.length; j++) { 
+                if (this.name == 'xaxis' || this.name == 'x2axis') {
+                    d[j][0] = new $.jsDate(d[j][0]).getTime();
+                    pd[j][0] = new $.jsDate(d[j][0]).getTime();
+                    sd[j][0] = new $.jsDate(d[j][0]).getTime();
+                    if ((d[j][0] != null && d[j][0] < db.min) || db.min == null) {
+                        db.min = d[j][0];
+                    }
+                    if ((d[j][0] != null && d[j][0] > db.max) || db.max == null) {
+                        db.max = d[j][0];
+                    }
+                    if (j>0) {
+                        intv = Math.abs(d[j][0] - d[j-1][0]);
+                        stats.intervals.push(intv);
+                        if (stats.frequencies.hasOwnProperty(intv)) {
+                            stats.frequencies[intv] += 1;
+                        }
+                        else {
+                            stats.frequencies[intv] = 1;
+                        }
+                    }
+                    sum += intv;
+                    
+                }              
+                else {
+                    d[j][1] = new $.jsDate(d[j][1]).getTime();
+                    pd[j][1] = new $.jsDate(d[j][1]).getTime();
+                    sd[j][1] = new $.jsDate(d[j][1]).getTime();
+                    if ((d[j][1] != null && d[j][1] < db.min) || db.min == null) {
+                        db.min = d[j][1];
+                    }
+                    if ((d[j][1] != null && d[j][1] > db.max) || db.max == null) {
+                        db.max = d[j][1];
+                    }
+                    if (j>0) {
+                        intv = Math.abs(d[j][1] - d[j-1][1]);
+                        stats.intervals.push(intv);
+                        if (stats.frequencies.hasOwnProperty(intv)) {
+                            stats.frequencies[intv] += 1;
+                        }
+                        else {
+                            stats.frequencies[intv] = 1;
+                        }
+                    }
+                }
+                sum += intv;              
+            }
+
+            if (s.renderer.bands) {
+                if (s.renderer.bands.hiData.length) {
+                    var bd = s.renderer.bands.hiData;
+                    for (var j=0, l=bd.length; j < l; j++) {
+                        if (this.name === 'xaxis' || this.name === 'x2axis') {
+                            bd[j][0] = new $.jsDate(bd[j][0]).getTime();
+                            if ((bd[j][0] != null && bd[j][0] > db.max) || db.max == null) {
+                                db.max = bd[j][0];
+                            }                        
+                        }              
+                        else {
+                            bd[j][1] = new $.jsDate(bd[j][1]).getTime();
+                            if ((bd[j][1] != null && bd[j][1] > db.max) || db.max == null) {
+                                db.max = bd[j][1];
+                            }
+                        }
+                    }
+                }
+                if (s.renderer.bands.lowData.length) {
+                    var bd = s.renderer.bands.lowData;
+                    for (var j=0, l=bd.length; j < l; j++) {
+                        if (this.name === 'xaxis' || this.name === 'x2axis') {
+                            bd[j][0] = new $.jsDate(bd[j][0]).getTime();
+                            if ((bd[j][0] != null && bd[j][0] < db.min) || db.min == null) {
+                                db.min = bd[j][0];
+                            }                       
+                        }              
+                        else {
+                            bd[j][1] = new $.jsDate(bd[j][1]).getTime();
+                            if ((bd[j][1] != null && bd[j][1] < db.min) || db.min == null) {
+                                db.min = bd[j][1];
+                            }
+                        }
+                    }
+                }
+            }
+            
+            var tempf = 0,
+                tempn=0;
+            for (var n in stats.frequencies) {
+                stats.sortedIntervals.push({interval:n, frequency:stats.frequencies[n]});
+            }
+            stats.sortedIntervals.sort(function(a, b){
+                return b.frequency - a.frequency;
+            });
+            
+            stats.min = $.jqplot.arrayMin(stats.intervals);
+            stats.max = $.jqplot.arrayMax(stats.intervals);
+            stats.mean = sum/d.length;
+            this._intervalStats.push(stats);
+            stats = sum = s = d = pd = sd = null;
+        }
+        db = null;
+        
+    };
+    
+    // called with scope of an axis
+    $.jqplot.DateAxisRenderer.prototype.reset = function() {
+        this.min = this._options.min;
+        this.max = this._options.max;
+        this.tickInterval = this._options.tickInterval;
+        this.numberTicks = this._options.numberTicks;
+        this._autoFormatString = '';
+        if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) {
+            this.tickOptions.formatString = '';
+        }
+        this.daTickInterval = this._daTickInterval;
+        // this._ticks = this.__ticks;
+    };
+    
+    $.jqplot.DateAxisRenderer.prototype.createTicks = function(plot) {
+        // we're are operating on an axis here
+        var ticks = this._ticks;
+        var userTicks = this.ticks;
+        var name = this.name;
+        // databounds were set on axis initialization.
+        var db = this._dataBounds;
+        var iv = this._intervalStats;
+        var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
+        var interval;
+        var min, max;
+        var pos1, pos2;
+        var tt, i;
+        var threshold = 30;
+        var insetMult = 1;
+        var daTickInterval = null;
+        
+        // if user specified a tick interval, convert to usable.
+        if (this.tickInterval != null)
+        {
+            // if interval is a number or can be converted to one, use it.
+            // Assume it is in SECONDS!!!
+            if (Number(this.tickInterval)) {
+                daTickInterval = [Number(this.tickInterval), 'seconds'];
+            }
+            // else, parse out something we can build from.
+            else if (typeof this.tickInterval == "string") {
+                var parts = this.tickInterval.split(' ');
+                if (parts.length == 1) {
+                    daTickInterval = [1, parts[0]];
+                }
+                else if (parts.length == 2) {
+                    daTickInterval = [parts[0], parts[1]];
+                }
+            }
+        }
+
+        var tickInterval = this.tickInterval;
+        
+        // if we already have ticks, use them.
+        // ticks must be in order of increasing value.
+        
+        min = new $.jsDate((this.min != null) ? this.min : db.min).getTime();
+        max = new $.jsDate((this.max != null) ? this.max : db.max).getTime();
+
+        // see if we're zooming.  if we are, don't use the min and max we're given,
+        // but compute some nice ones.  They will be reset later.
+
+        var cursor = plot.plugins.cursor;
+
+        if (cursor && cursor._zoom && cursor._zoom.zooming) {
+            this.min = null;
+            this.max = null;
+        }
+
+        var range = max - min;
+
+        if (this.tickOptions == null || !this.tickOptions.formatString) {
+            this._overrideFormatString = true;
+        }
+        
+        if (userTicks.length) {
+            // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
+            for (i=0; i<userTicks.length; i++){
+                var ut = userTicks[i];
+                var t = new this.tickRenderer(this.tickOptions);
+                if (ut.constructor == Array) {
+                    t.value = new $.jsDate(ut[0]).getTime();
+                    t.label = ut[1];
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    t.setTick(t.value, this.name);
+                    this._ticks.push(t);
+                }
+                
+                else {
+                    t.value = new $.jsDate(ut).getTime();
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    t.setTick(t.value, this.name);
+                    this._ticks.push(t);
+                }
+            }
+            this.numberTicks = userTicks.length;
+            this.min = this._ticks[0].value;
+            this.max = this._ticks[this.numberTicks-1].value;
+            this.daTickInterval = [(this.max - this.min) / (this.numberTicks - 1)/1000, 'seconds'];
+        }
+
+        ////////
+        // We don't have any ticks yet, let's make some!
+        ////////
+
+        // special case when there is only one point, make three tick marks to center the point
+        else if (this.min == null && this.max == null && db.min == db.max)
+        {
+             var onePointOpts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
+             var delta = 300000;
+             this.min = db.min - delta;
+             this.max = db.max + delta;
+             this.numberTicks = 3;
+
+             for(var i=this.min;i<=this.max;i+= delta)
+             {
+                 onePointOpts.value = i;
+
+                 var t = new this.tickRenderer(onePointOpts);
+
+                 if (this._overrideFormatString && this._autoFormatString != '') {
+                    t.formatString = this._autoFormatString;
+                 }
+
+                 t.showLabel = false;
+                 t.showMark = false;
+
+                 this._ticks.push(t);
+             }
+
+             if(this.showTicks) {
+                 this._ticks[1].showLabel = true;
+             }
+             if(this.showTickMarks) {
+                 this._ticks[1].showTickMarks = true;
+             }                   
+        }
+        // if user specified min and max are null, we set those to make best ticks.
+        else if (this.min == null && this.max == null) {
+
+            var opts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
+
+            // want to find a nice interval 
+            var nttarget,
+                titarget;
+
+            // if no tickInterval or numberTicks options specified,  make a good guess.
+            if (!this.tickInterval && !this.numberTicks) {
+                var tdim = Math.max(dim, threshold+1);
+                // how many ticks to put on the axis?
+                // date labels tend to be long.  If ticks not rotated,
+                // don't use too many and have a high spacing factor.
+                // If we are rotating ticks, use a lower factor.
+                var spacingFactor = 115;
+                if (this.tickRenderer === $.jqplot.CanvasAxisTickRenderer && this.tickOptions.angle) {
+                    spacingFactor = 115 - 40 * Math.abs(Math.sin(this.tickOptions.angle/180*Math.PI));
+                }
+
+                nttarget =  Math.ceil((tdim-threshold)/spacingFactor + 1);
+                titarget = (max - min) / (nttarget - 1);
+            }
+
+            // If tickInterval is specified, we'll try to honor it.
+            // Not guaranteed to get this interval, but we'll get as close as
+            // we can.
+            // tickInterval will be used before numberTicks, that is if
+            // both are specified, numberTicks will be ignored.
+            else if (this.tickInterval) {
+                titarget = new $.jsDate(0).add(daTickInterval[0], daTickInterval[1]).getTime();
+            }
+
+            // if numberTicks specified, try to honor it.
+            // Not guaranteed, but will try to get close.
+            else if (this.numberTicks) {
+                nttarget = this.numberTicks;
+                titarget = (max - min) / (nttarget - 1);
+            }
+
+            // If we can use an interval of 2 weeks or less, pick best one
+            if (titarget <= 19*day) {
+                var ret = bestDateInterval(min, max, titarget);
+                var tempti = ret[0];
+                this._autoFormatString = ret[1];
+
+                min = new $.jsDate(min);
+                min = Math.floor((min.getTime() - min.getUtcOffset())/tempti) * tempti + min.getUtcOffset();
+
+                nttarget = Math.ceil((max - min) / tempti) + 1;
+                this.min = min;
+                this.max = min + (nttarget - 1) * tempti;
+
+                // if max is less than max, add an interval
+                if (this.max < max) {
+                    this.max += tempti;
+                    nttarget += 1;
+                }
+                this.tickInterval = tempti;
+                this.numberTicks = nttarget;
+
+                for (var i=0; i<nttarget; i++) {
+                    opts.value = this.min + i * tempti;
+                    t = new this.tickRenderer(opts);
+                    
+                    if (this._overrideFormatString && this._autoFormatString != '') {
+                        t.formatString = this._autoFormatString;
+                    }
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    this._ticks.push(t);
+                }
+
+                insetMult = this.tickInterval;
+            }
+
+            // should we use a monthly interval?
+            else if (titarget <= 9 * month) {
+
+                this._autoFormatString = '%v';
+
+                // how many months in an interval?
+                var intv = Math.round(titarget/month);
+                if (intv < 1) {
+                    intv = 1;
+                }
+                else if (intv > 6) {
+                    intv = 6;
+                }
+
+                // figure out the starting month and ending month.
+                var mstart = new $.jsDate(min).setDate(1).setHours(0,0,0,0);
+
+                // See if max ends exactly on a month
+                var tempmend = new $.jsDate(max);
+                var mend = new $.jsDate(max).setDate(1).setHours(0,0,0,0);
+
+                if (tempmend.getTime() !== mend.getTime()) {
+                    mend = mend.add(1, 'month');
+                }
+
+                var nmonths = mend.diff(mstart, 'month');
+
+                nttarget = Math.ceil(nmonths/intv) + 1;
+
+                this.min = mstart.getTime();
+                this.max = mstart.clone().add((nttarget - 1) * intv, 'month').getTime();
+                this.numberTicks = nttarget;
+
+                for (var i=0; i<nttarget; i++) {
+                    if (i === 0) {
+                        opts.value = mstart.getTime();
+                    }
+                    else {
+                        opts.value = mstart.add(intv, 'month').getTime();
+                    }
+                    t = new this.tickRenderer(opts);
+                    
+                    if (this._overrideFormatString && this._autoFormatString != '') {
+                        t.formatString = this._autoFormatString;
+                    }
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    this._ticks.push(t);
+                }
+
+                insetMult = intv * month;
+            }
+
+            // use yearly intervals
+            else {
+
+                this._autoFormatString = '%v';
+
+                // how many years in an interval?
+                var intv = Math.round(titarget/year);
+                if (intv < 1) {
+                    intv = 1;
+                }
+
+                // figure out the starting and ending years.
+                var mstart = new $.jsDate(min).setMonth(0, 1).setHours(0,0,0,0);
+                var mend = new $.jsDate(max).add(1, 'year').setMonth(0, 1).setHours(0,0,0,0);
+
+                var nyears = mend.diff(mstart, 'year');
+
+                nttarget = Math.ceil(nyears/intv) + 1;
+
+                this.min = mstart.getTime();
+                this.max = mstart.clone().add((nttarget - 1) * intv, 'year').getTime();
+                this.numberTicks = nttarget;
+
+                for (var i=0; i<nttarget; i++) {
+                    if (i === 0) {
+                        opts.value = mstart.getTime();
+                    }
+                    else {
+                        opts.value = mstart.add(intv, 'year').getTime();
+                    }
+                    t = new this.tickRenderer(opts);
+                    
+                    if (this._overrideFormatString && this._autoFormatString != '') {
+                        t.formatString = this._autoFormatString;
+                    }
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    this._ticks.push(t);
+                }
+
+                insetMult = intv * year;
+            }
+        }
+
+        ////////
+        // Some option(s) specified, work around that.
+        ////////
+        
+        else {      
+            if (name == 'xaxis' || name == 'x2axis') {
+                dim = this._plotDimensions.width;
+            }
+            else {
+                dim = this._plotDimensions.height;
+            }
+            
+            // if min, max and number of ticks specified, user can't specify interval.
+            if (this.min != null && this.max != null && this.numberTicks != null) {
+                this.tickInterval = null;
+            }
+            
+            if (this.tickInterval != null && daTickInterval != null) {
+                this.daTickInterval = daTickInterval;
+            }
+            
+            // if min and max are same, space them out a bit
+            if (min == max) {
+                var adj = 24*60*60*500;  // 1/2 day
+                min -= adj;
+                max += adj;
+            }
+
+            range = max - min;
+            
+            var optNumTicks = 2 + parseInt(Math.max(0, dim-100)/100, 10);
+            
+            
+            var rmin, rmax;
+            
+            rmin = (this.min != null) ? new $.jsDate(this.min).getTime() : min - range/2*(this.padMin - 1);
+            rmax = (this.max != null) ? new $.jsDate(this.max).getTime() : max + range/2*(this.padMax - 1);
+            this.min = rmin;
+            this.max = rmax;
+            range = this.max - this.min;
+            
+            if (this.numberTicks == null){
+                // if tickInterval is specified by user, we will ignore computed maximum.
+                // max will be equal or greater to fit even # of ticks.
+                if (this.daTickInterval != null) {
+                    var nc = new $.jsDate(this.max).diff(this.min, this.daTickInterval[1], true);
+                    this.numberTicks = Math.ceil(nc/this.daTickInterval[0]) +1;
+                    // this.max = new $.jsDate(this.min).add(this.numberTicks-1, this.daTickInterval[1]).getTime();
+                    this.max = new $.jsDate(this.min).add((this.numberTicks-1) * this.daTickInterval[0], this.daTickInterval[1]).getTime();
+                }
+                else if (dim > 200) {
+                    this.numberTicks = parseInt(3+(dim-200)/100, 10);
+                }
+                else {
+                    this.numberTicks = 2;
+                }
+            }
+            
+            insetMult = range / (this.numberTicks-1)/1000;
+
+            if (this.daTickInterval == null) {
+                this.daTickInterval = [insetMult, 'seconds'];
+            }
+
+
+            for (var i=0; i<this.numberTicks; i++){
+                var min = new $.jsDate(this.min);
+                tt = min.add(i*this.daTickInterval[0], this.daTickInterval[1]).getTime();
+                var t = new this.tickRenderer(this.tickOptions);
+                // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
+                if (!this.showTicks) {
+                    t.showLabel = false;
+                    t.showMark = false;
+                }
+                else if (!this.showTickMarks) {
+                    t.showMark = false;
+                }
+                t.setTick(tt, this.name);
+                this._ticks.push(t);
+            }
+        }
+
+        if (this.tickInset) {
+            this.min = this.min - this.tickInset * insetMult;
+            this.max = this.max + this.tickInset * insetMult;
+        }
+
+        if (this._daTickInterval == null) {
+            this._daTickInterval = this.daTickInterval;    
+        }
+
+        ticks = null;
+    };
+   
+})(jQuery);
+
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.donutRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.donutRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.donutRenderer.js	(revision 2119)
@@ -0,0 +1,805 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    /**
+     * Class: $.jqplot.DonutRenderer
+     * Plugin renderer to draw a donut chart.
+     * x values, if present, will be used as slice labels.
+     * y values give slice size.
+     * 
+     * To use this renderer, you need to include the 
+     * donut renderer plugin, for example:
+     * 
+     * > <script type="text/javascript" src="plugins/jqplot.donutRenderer.js"></script>
+     * 
+     * Properties described here are passed into the $.jqplot function
+     * as options on the series renderer.  For example:
+     * 
+     * > plot2 = $.jqplot('chart2', [s1, s2], {
+     * >     seriesDefaults: {
+     * >         renderer:$.jqplot.DonutRenderer,
+     * >         rendererOptions:{
+     * >              sliceMargin: 2,
+     * >              innerDiameter: 110,
+     * >              startAngle: -90
+     * >          }
+     * >      }
+     * > });
+     * 
+     * A donut plot will trigger events on the plot target
+     * according to user interaction.  All events return the event object,
+     * the series index, the point (slice) index, and the point data for 
+     * the appropriate slice.
+     * 
+     * 'jqplotDataMouseOver' - triggered when user mouseing over a slice.
+     * 'jqplotDataHighlight' - triggered the first time user mouses over a slice,
+     * if highlighting is enabled.
+     * 'jqplotDataUnhighlight' - triggered when a user moves the mouse out of
+     * a highlighted slice.
+     * 'jqplotDataClick' - triggered when the user clicks on a slice.
+     * 'jqplotDataRightClick' - tiggered when the user right clicks on a slice if
+     * the "captureRightClick" option is set to true on the plot.
+     */
+    $.jqplot.DonutRenderer = function(){
+        $.jqplot.LineRenderer.call(this);
+    };
+    
+    $.jqplot.DonutRenderer.prototype = new $.jqplot.LineRenderer();
+    $.jqplot.DonutRenderer.prototype.constructor = $.jqplot.DonutRenderer;
+    
+    // called with scope of a series
+    $.jqplot.DonutRenderer.prototype.init = function(options, plot) {
+        // Group: Properties
+        //
+        // prop: diameter
+        // Outer diameter of the donut, auto computed by default
+        this.diameter = null;
+        // prop: innerDiameter
+        // Inner diameter of the donut, auto calculated by default.
+        // If specified will override thickness value.
+        this.innerDiameter = null;
+        // prop: thickness
+        // thickness of the donut, auto computed by default
+        // Overridden by if innerDiameter is specified.
+        this.thickness = null;
+        // prop: padding
+        // padding between the donut and plot edges, legend, etc.
+        this.padding = 20;
+        // prop: sliceMargin
+        // angular spacing between donut slices in degrees.
+        this.sliceMargin = 0;
+        // prop: ringMargin
+        // pixel distance between rings, or multiple series in a donut plot.
+        // null will compute ringMargin based on sliceMargin.
+        this.ringMargin = null;
+        // prop: fill
+        // true or false, whether to fil the slices.
+        this.fill = true;
+        // prop: shadowOffset
+        // offset of the shadow from the slice and offset of 
+        // each succesive stroke of the shadow from the last.
+        this.shadowOffset = 2;
+        // prop: shadowAlpha
+        // transparency of the shadow (0 = transparent, 1 = opaque)
+        this.shadowAlpha = 0.07;
+        // prop: shadowDepth
+        // number of strokes to apply to the shadow, 
+        // each stroke offset shadowOffset from the last.
+        this.shadowDepth = 5;
+        // prop: highlightMouseOver
+        // True to highlight slice when moused over.
+        // This must be false to enable highlightMouseDown to highlight when clicking on a slice.
+        this.highlightMouseOver = true;
+        // prop: highlightMouseDown
+        // True to highlight when a mouse button is pressed over a slice.
+        // This will be disabled if highlightMouseOver is true.
+        this.highlightMouseDown = false;
+        // prop: highlightColors
+        // an array of colors to use when highlighting a slice.
+        this.highlightColors = [];
+        // prop: dataLabels
+        // Either 'label', 'value', 'percent' or an array of labels to place on the pie slices.
+        // Defaults to percentage of each pie slice.
+        this.dataLabels = 'percent';
+        // prop: showDataLabels
+        // true to show data labels on slices.
+        this.showDataLabels = false;
+        // prop: dataLabelFormatString
+        // Format string for data labels.  If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage.
+        this.dataLabelFormatString = null;
+        // prop: dataLabelThreshold
+        // Threshhold in percentage (0 - 100) of pie area, below which no label will be displayed.
+        // This applies to all label types, not just to percentage labels.
+        this.dataLabelThreshold = 3;
+        // prop: dataLabelPositionFactor
+        // A Multiplier (0-1) of the pie radius which controls position of label on slice.
+        // Increasing will slide label toward edge of pie, decreasing will slide label toward center of pie.
+        this.dataLabelPositionFactor = 0.4;
+        // prop: dataLabelNudge
+        // Number of pixels to slide the label away from (+) or toward (-) the center of the pie.
+        this.dataLabelNudge = 0;
+        // prop: startAngle
+        // Angle to start drawing donut in degrees.  
+        // According to orientation of canvas coordinate system:
+        // 0 = on the positive x axis
+        // -90 = on the positive y axis.
+        // 90 = on the negaive y axis.
+        // 180 or - 180 = on the negative x axis.
+        this.startAngle = 0;
+        this.tickRenderer = $.jqplot.DonutTickRenderer;
+        // Used as check for conditions where donut shouldn't be drawn.
+        this._drawData = true;
+        this._type = 'donut';
+        
+        // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
+        if (options.highlightMouseDown && options.highlightMouseOver == null) {
+            options.highlightMouseOver = false;
+        }
+        
+        $.extend(true, this, options);
+        if (this.diameter != null) {
+            this.diameter = this.diameter - this.sliceMargin;
+        }
+        this._diameter = null;
+        this._innerDiameter = null;
+        this._radius = null;
+        this._innerRadius = null;
+        this._thickness = null;
+        // references to the previous series in the plot to properly calculate diameters
+        // and thicknesses of nested rings.
+        this._previousSeries = [];
+        this._numberSeries = 1;
+        // array of [start,end] angles arrays, one for each slice.  In radians.
+        this._sliceAngles = [];
+        // index of the currenty highlighted point, if any
+        this._highlightedPoint = null;
+        
+        // set highlight colors if none provided
+        if (this.highlightColors.length == 0) {
+            for (var i=0; i<this.seriesColors.length; i++){
+                var rgba = $.jqplot.getColorComponents(this.seriesColors[i]);
+                var newrgb = [rgba[0], rgba[1], rgba[2]];
+                var sum = newrgb[0] + newrgb[1] + newrgb[2];
+                for (var j=0; j<3; j++) {
+                    // when darkening, lowest color component can be is 60.
+                    newrgb[j] = (sum > 570) ?  newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
+                    newrgb[j] = parseInt(newrgb[j], 10);
+                }
+                this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
+            }
+        }
+        
+        plot.postParseOptionsHooks.addOnce(postParseOptions);
+        plot.postInitHooks.addOnce(postInit);
+        plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
+        plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
+        plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
+        plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
+        plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
+        plot.postDrawHooks.addOnce(postPlotDraw);
+        
+        
+    };
+    
+    $.jqplot.DonutRenderer.prototype.setGridData = function(plot) {
+        // set gridData property.  This will hold angle in radians of each data point.
+        var stack = [];
+        var td = [];
+        var sa = this.startAngle/180*Math.PI;
+        var tot = 0;
+        // don't know if we have any valid data yet, so set plot to not draw.
+        this._drawData = false;
+        for (var i=0; i<this.data.length; i++){
+            if (this.data[i][1] != 0) {
+                // we have data, O.K. to draw.
+                this._drawData = true;
+            }
+            stack.push(this.data[i][1]);
+            td.push([this.data[i][0]]);
+            if (i>0) {
+                stack[i] += stack[i-1];
+            }
+            tot += this.data[i][1];
+        }
+        var fact = Math.PI*2/stack[stack.length - 1];
+        
+        for (var i=0; i<stack.length; i++) {
+            td[i][1] = stack[i] * fact;
+            td[i][2] = this.data[i][1]/tot;
+        }
+        this.gridData = td;
+    };
+    
+    $.jqplot.DonutRenderer.prototype.makeGridData = function(data, plot) {
+        var stack = [];
+        var td = [];
+        var tot = 0;
+        var sa = this.startAngle/180*Math.PI;
+        // don't know if we have any valid data yet, so set plot to not draw.
+        this._drawData = false;
+        for (var i=0; i<data.length; i++){
+            if (this.data[i][1] != 0) {
+                // we have data, O.K. to draw.
+                this._drawData = true;
+            }
+            stack.push(data[i][1]);
+            td.push([data[i][0]]);
+            if (i>0) {
+                stack[i] += stack[i-1];
+            }
+            tot += data[i][1];
+        }
+        var fact = Math.PI*2/stack[stack.length - 1];
+        
+        for (var i=0; i<stack.length; i++) {
+            td[i][1] = stack[i] * fact;
+            td[i][2] = data[i][1]/tot;
+        }
+        return td;
+    };
+    
+    $.jqplot.DonutRenderer.prototype.drawSlice = function (ctx, ang1, ang2, color, isShadow) {
+        var r = this._diameter / 2;
+        var ri = r - this._thickness;
+        var fill = this.fill;
+        // var lineWidth = this.lineWidth;
+        ctx.save();
+        ctx.translate(this._center[0], this._center[1]);
+        // ctx.translate(this.sliceMargin*Math.cos((ang1+ang2)/2), this.sliceMargin*Math.sin((ang1+ang2)/2));
+        
+        if (isShadow) {
+            for (var i=0; i<this.shadowDepth; i++) {
+                ctx.save();
+                ctx.translate(this.shadowOffset*Math.cos(this.shadowAngle/180*Math.PI), this.shadowOffset*Math.sin(this.shadowAngle/180*Math.PI));
+                doDraw();
+            }
+        }
+        
+        else {
+            doDraw();
+        }
+        
+        function doDraw () {
+            // Fix for IE and Chrome that can't seem to draw circles correctly.
+            // ang2 should always be <= 2 pi since that is the way the data is converted.
+             if (ang2 > 6.282 + this.startAngle) {
+                ang2 = 6.282 + this.startAngle;
+                if (ang1 > ang2) {
+                    ang1 = 6.281 + this.startAngle;
+                }
+            }
+            // Fix for IE, where it can't seem to handle 0 degree angles.  Also avoids
+            // ugly line on unfilled donuts.
+            if (ang1 >= ang2) {
+                return;
+            }
+            ctx.beginPath();  
+            ctx.fillStyle = color;
+            ctx.strokeStyle = color;
+            // ctx.lineWidth = lineWidth;
+            ctx.arc(0, 0, r, ang1, ang2, false);
+            ctx.lineTo(ri*Math.cos(ang2), ri*Math.sin(ang2));
+            ctx.arc(0,0, ri, ang2, ang1, true);
+            ctx.closePath();
+            if (fill) {
+                ctx.fill();
+            }
+            else {
+                ctx.stroke();
+            }
+        }
+        
+        if (isShadow) {
+            for (var i=0; i<this.shadowDepth; i++) {
+                ctx.restore();
+            }
+        }
+        
+        ctx.restore();
+    };
+    
+    // called with scope of series
+    $.jqplot.DonutRenderer.prototype.draw = function (ctx, gd, options, plot) {
+        var i;
+        var opts = (options != undefined) ? options : {};
+        // offset and direction of offset due to legend placement
+        var offx = 0;
+        var offy = 0;
+        var trans = 1;
+        // var colorGenerator = new this.colorGenerator(this.seriesColors);
+        if (options.legendInfo && options.legendInfo.placement == 'insideGrid') {
+            var li = options.legendInfo;
+            switch (li.location) {
+                case 'nw':
+                    offx = li.width + li.xoffset;
+                    break;
+                case 'w':
+                    offx = li.width + li.xoffset;
+                    break;
+                case 'sw':
+                    offx = li.width + li.xoffset;
+                    break;
+                case 'ne':
+                    offx = li.width + li.xoffset;
+                    trans = -1;
+                    break;
+                case 'e':
+                    offx = li.width + li.xoffset;
+                    trans = -1;
+                    break;
+                case 'se':
+                    offx = li.width + li.xoffset;
+                    trans = -1;
+                    break;
+                case 'n':
+                    offy = li.height + li.yoffset;
+                    break;
+                case 's':
+                    offy = li.height + li.yoffset;
+                    trans = -1;
+                    break;
+                default:
+                    break;
+            }
+        }
+        
+        var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+        var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
+        var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+        var cw = ctx.canvas.width;
+        var ch = ctx.canvas.height;
+        var w = cw - offx - 2 * this.padding;
+        var h = ch - offy - 2 * this.padding;
+        var mindim = Math.min(w,h);
+        var d = mindim;
+        var ringmargin =  (this.ringMargin == null) ? this.sliceMargin * 2.0 : this.ringMargin;
+        
+        for (var i=0; i<this._previousSeries.length; i++) {
+            d -= 2.0 * this._previousSeries[i]._thickness + 2.0 * ringmargin;
+        }
+        this._diameter = this.diameter || d;
+        if (this.innerDiameter != null) {
+            var od = (this._numberSeries > 1 && this.index > 0) ? this._previousSeries[0]._diameter : this._diameter;
+            this._thickness = this.thickness || (od - this.innerDiameter - 2.0*ringmargin*this._numberSeries) / this._numberSeries/2.0;
+        }
+        else {
+            this._thickness = this.thickness || mindim / 2 / (this._numberSeries + 1) * 0.85;
+        }
+
+        var r = this._radius = this._diameter/2;
+        this._innerRadius = this._radius - this._thickness;
+        var sa = this.startAngle / 180 * Math.PI;
+        this._center = [(cw - trans * offx)/2 + trans * offx, (ch - trans*offy)/2 + trans * offy];
+        
+        if (this.shadow) {
+            var shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')';
+            for (var i=0; i<gd.length; i++) {
+                var ang1 = (i == 0) ? sa : gd[i-1][1] + sa;
+                // Adjust ang1 and ang2 for sliceMargin
+                ang1 += this.sliceMargin/180*Math.PI;
+                this.renderer.drawSlice.call (this, ctx, ang1, gd[i][1]+sa, shadowColor, true);
+            }
+            
+        }
+        for (var i=0; i<gd.length; i++) {
+            var ang1 = (i == 0) ? sa : gd[i-1][1] + sa;
+            // Adjust ang1 and ang2 for sliceMargin
+            ang1 += this.sliceMargin/180*Math.PI;
+            var ang2 = gd[i][1] + sa;
+            this._sliceAngles.push([ang1, ang2]);
+            this.renderer.drawSlice.call (this, ctx, ang1, ang2, this.seriesColors[i], false);
+            
+            if (this.showDataLabels && gd[i][2]*100 >= this.dataLabelThreshold) {
+                var fstr, avgang = (ang1+ang2)/2, label;
+                
+                if (this.dataLabels == 'label') {
+                    fstr = this.dataLabelFormatString || '%s';
+                    label = $.jqplot.sprintf(fstr, gd[i][0]);
+                }
+                else if (this.dataLabels == 'value') {
+                    fstr = this.dataLabelFormatString || '%d';
+                    label = $.jqplot.sprintf(fstr, this.data[i][1]);
+                }
+                else if (this.dataLabels == 'percent') {
+                    fstr = this.dataLabelFormatString || '%d%%';
+                    label = $.jqplot.sprintf(fstr, gd[i][2]*100);
+                }
+                else if (this.dataLabels.constructor == Array) {
+                    fstr = this.dataLabelFormatString || '%s';
+                    label = $.jqplot.sprintf(fstr, this.dataLabels[i]);
+                }
+                
+                var fact = this._innerRadius + this._thickness * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
+                
+                var x = this._center[0] + Math.cos(avgang) * fact + this.canvas._offsets.left;
+                var y = this._center[1] + Math.sin(avgang) * fact + this.canvas._offsets.top;
+                
+                var labelelem = $('<span class="jqplot-donut-series jqplot-data-label" style="position:absolute;">' + label + '</span>').insertBefore(plot.eventCanvas._elem);
+                x -= labelelem.width()/2;
+                y -= labelelem.height()/2;
+                x = Math.round(x);
+                y = Math.round(y);
+                labelelem.css({left: x, top: y});
+            }
+        }
+               
+    };
+    
+    $.jqplot.DonutAxisRenderer = function() {
+        $.jqplot.LinearAxisRenderer.call(this);
+    };
+    
+    $.jqplot.DonutAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+    $.jqplot.DonutAxisRenderer.prototype.constructor = $.jqplot.DonutAxisRenderer;
+        
+    
+    // There are no traditional axes on a donut chart.  We just need to provide
+    // dummy objects with properties so the plot will render.
+    // called with scope of axis object.
+    $.jqplot.DonutAxisRenderer.prototype.init = function(options){
+        //
+        this.tickRenderer = $.jqplot.DonutTickRenderer;
+        $.extend(true, this, options);
+        // I don't think I'm going to need _dataBounds here.
+        // have to go Axis scaling in a way to fit chart onto plot area
+        // and provide u2p and p2u functionality for mouse cursor, etc.
+        // for convienence set _dataBounds to 0 and 100 and
+        // set min/max to 0 and 100.
+        this._dataBounds = {min:0, max:100};
+        this.min = 0;
+        this.max = 100;
+        this.showTicks = false;
+        this.ticks = [];
+        this.showMark = false;
+        this.show = false; 
+    };
+    
+    
+    
+    
+    $.jqplot.DonutLegendRenderer = function(){
+        $.jqplot.TableLegendRenderer.call(this);
+    };
+    
+    $.jqplot.DonutLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
+    $.jqplot.DonutLegendRenderer.prototype.constructor = $.jqplot.DonutLegendRenderer;
+    
+    /**
+     * Class: $.jqplot.DonutLegendRenderer
+     * Legend Renderer specific to donut plots.  Set by default
+     * when user creates a donut plot.
+     */
+    $.jqplot.DonutLegendRenderer.prototype.init = function(options) {
+        // Group: Properties
+        //
+        // prop: numberRows
+        // Maximum number of rows in the legend.  0 or null for unlimited.
+        this.numberRows = null;
+        // prop: numberColumns
+        // Maximum number of columns in the legend.  0 or null for unlimited.
+        this.numberColumns = null;
+        $.extend(true, this, options);
+    };
+    
+    // called with context of legend
+    $.jqplot.DonutLegendRenderer.prototype.draw = function() {
+        var legend = this;
+        if (this.show) {
+            var series = this._series;
+            var ss = 'position:absolute;';
+            ss += (this.background) ? 'background:'+this.background+';' : '';
+            ss += (this.border) ? 'border:'+this.border+';' : '';
+            ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
+            ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
+            ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
+            ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
+            ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
+            ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
+            ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
+            this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>');
+            // Donut charts legends don't go by number of series, but by number of data points
+            // in the series.  Refactor things here for that.
+            
+            var pad = false, 
+                reverse = false,
+                nr, nc;
+            var s = series[0];
+            var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
+            
+            if (s.show) {
+                var pd = s.data;
+                if (this.numberRows) {
+                    nr = this.numberRows;
+                    if (!this.numberColumns){
+                        nc = Math.ceil(pd.length/nr);
+                    }
+                    else{
+                        nc = this.numberColumns;
+                    }
+                }
+                else if (this.numberColumns) {
+                    nc = this.numberColumns;
+                    nr = Math.ceil(pd.length/this.numberColumns);
+                }
+                else {
+                    nr = pd.length;
+                    nc = 1;
+                }
+                
+                var i, j, tr, td1, td2, lt, rs, color;
+                var idx = 0;    
+                
+                for (i=0; i<nr; i++) {
+                    if (reverse){
+                        tr = $('<tr class="jqplot-table-legend"></tr>').prependTo(this._elem);
+                    }
+                    else{
+                        tr = $('<tr class="jqplot-table-legend"></tr>').appendTo(this._elem);
+                    }
+                    for (j=0; j<nc; j++) {
+                        if (idx < pd.length){
+                            lt = this.labels[idx] || pd[idx][0].toString();
+                            color = colorGenerator.next();
+                            if (!reverse){
+                                if (i>0){
+                                    pad = true;
+                                }
+                                else{
+                                    pad = false;
+                                }
+                            }
+                            else{
+                                if (i == nr -1){
+                                    pad = false;
+                                }
+                                else{
+                                    pad = true;
+                                }
+                            }
+                            rs = (pad) ? this.rowSpacing : '0';
+                
+                            td1 = $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
+                                '<div><div class="jqplot-table-legend-swatch" style="border-color:'+color+';"></div>'+
+                                '</div></td>');
+                            td2 = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
+                            if (this.escapeHtml){
+                                td2.text(lt);
+                            }
+                            else {
+                                td2.html(lt);
+                            }
+                            if (reverse) {
+                                td2.prependTo(tr);
+                                td1.prependTo(tr);
+                            }
+                            else {
+                                td1.appendTo(tr);
+                                td2.appendTo(tr);
+                            }
+                            pad = true;
+                        }
+                        idx++;
+                    }   
+                }
+            }
+        }
+        return this._elem;                
+    };
+    
+    // setup default renderers for axes and legend so user doesn't have to
+    // called with scope of plot
+    function preInit(target, data, options) {
+        options = options || {};
+        options.axesDefaults = options.axesDefaults || {};
+        options.legend = options.legend || {};
+        options.seriesDefaults = options.seriesDefaults || {};
+        // only set these if there is a donut series
+        var setopts = false;
+        if (options.seriesDefaults.renderer == $.jqplot.DonutRenderer) {
+            setopts = true;
+        }
+        else if (options.series) {
+            for (var i=0; i < options.series.length; i++) {
+                if (options.series[i].renderer == $.jqplot.DonutRenderer) {
+                    setopts = true;
+                }
+            }
+        }
+        
+        if (setopts) {
+            options.axesDefaults.renderer = $.jqplot.DonutAxisRenderer;
+            options.legend.renderer = $.jqplot.DonutLegendRenderer;
+            options.legend.preDraw = true;
+            options.seriesDefaults.pointLabels = {show: false};
+        }
+    }
+    
+    // called with scope of plot.
+    function postInit(target, data, options) {
+        // if multiple series, add a reference to the previous one so that
+        // donut rings can nest.
+        for (var i=1; i<this.series.length; i++) {
+            if (!this.series[i]._previousSeries.length){
+                for (var j=0; j<i; j++) {
+                    if (this.series[i].renderer.constructor == $.jqplot.DonutRenderer && this.series[j].renderer.constructor == $.jqplot.DonutRenderer) {
+                        this.series[i]._previousSeries.push(this.series[j]);
+                    }
+                }
+            }
+        }
+        for (i=0; i<this.series.length; i++) {
+            if (this.series[i].renderer.constructor == $.jqplot.DonutRenderer) {
+                this.series[i]._numberSeries = this.series.length;
+                // don't allow mouseover and mousedown at same time.
+                if (this.series[i].highlightMouseOver) {
+                    this.series[i].highlightMouseDown = false;
+                }
+            }
+        }
+    }
+    
+    var postParseOptionsRun = false;
+    // called with scope of plot
+    function postParseOptions(options) {
+        for (var i=0; i<this.series.length; i++) {
+            this.series[i].seriesColors = this.seriesColors;
+            this.series[i].colorGenerator = $.jqplot.colorGenerator;
+        }
+    }
+    
+    function highlight (plot, sidx, pidx) {
+        var s = plot.series[sidx];
+        var canvas = plot.plugins.donutRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        s._highlightedPoint = pidx;
+        plot.plugins.donutRenderer.highlightedSeriesIndex = sidx;
+        s.renderer.drawSlice.call(s, canvas._ctx, s._sliceAngles[pidx][0], s._sliceAngles[pidx][1], s.highlightColors[pidx], false);
+    }
+    
+    function unhighlight (plot) {
+        var canvas = plot.plugins.donutRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        for (var i=0; i<plot.series.length; i++) {
+            plot.series[i]._highlightedPoint = null;
+        }
+        plot.plugins.donutRenderer.highlightedSeriesIndex = null;
+        plot.target.trigger('jqplotDataUnhighlight');
+    }
+ 
+    function handleMove(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var evt1 = jQuery.Event('jqplotDataMouseOver');
+            evt1.pageX = ev.pageX;
+            evt1.pageY = ev.pageY;
+            plot.target.trigger(evt1, ins);
+            if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.donutRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, ins[0], ins[1]);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    } 
+    
+    function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.donutRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, ins[0], ins[1]);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    }
+    
+    function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
+        var idx = plot.plugins.donutRenderer.highlightedSeriesIndex;
+        if (idx != null && plot.series[idx].highlightMouseDown) {
+            unhighlight(plot);
+        }
+    }
+    
+    function handleClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var evt = jQuery.Event('jqplotDataClick');
+            evt.which = ev.which;
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            plot.target.trigger(evt, ins);
+        }
+    }
+    
+    function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var idx = plot.plugins.donutRenderer.highlightedSeriesIndex;
+            if (idx != null && plot.series[idx].highlightMouseDown) {
+                unhighlight(plot);
+            }
+            var evt = jQuery.Event('jqplotDataRightClick');
+            evt.which = ev.which;
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            plot.target.trigger(evt, ins);
+        }
+    }    
+    
+    // called within context of plot
+    // create a canvas which we can draw on.
+    // insert it before the eventCanvas, so eventCanvas will still capture events.
+    function postPlotDraw() {
+        // Memory Leaks patch    
+        if (this.plugins.donutRenderer && this.plugins.donutRenderer.highlightCanvas) {
+            this.plugins.donutRenderer.highlightCanvas.resetCanvas();
+            this.plugins.donutRenderer.highlightCanvas = null;
+        }
+
+        this.plugins.donutRenderer = {highlightedSeriesIndex:null};
+        this.plugins.donutRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
+        // do we have any data labels?  if so, put highlight canvas before those
+        // Fix for broken jquery :first selector with canvas (VML) elements.
+        var labels = $(this.targetId+' .jqplot-data-label');
+        if (labels.length) {
+            $(labels[0]).before(this.plugins.donutRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-donutRenderer-highlight-canvas', this._plotDimensions, this));
+        }
+        // else put highlight canvas before event canvas.
+        else {
+            this.eventCanvas._elem.before(this.plugins.donutRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-donutRenderer-highlight-canvas', this._plotDimensions, this));
+        }
+        var hctx = this.plugins.donutRenderer.highlightCanvas.setContext();
+        this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
+    }
+    
+    $.jqplot.preInitHooks.push(preInit);
+    
+    $.jqplot.DonutTickRenderer = function() {
+        $.jqplot.AxisTickRenderer.call(this);
+    };
+    
+    $.jqplot.DonutTickRenderer.prototype = new $.jqplot.AxisTickRenderer();
+    $.jqplot.DonutTickRenderer.prototype.constructor = $.jqplot.DonutTickRenderer;
+    
+})(jQuery);
+    
+    
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.dragable.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.dragable.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.dragable.js	(revision 2119)
@@ -0,0 +1,225 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    
+    /**
+     * Class: $.jqplot.Dragable
+     * Plugin to make plotted points dragable by the user.
+     */
+    $.jqplot.Dragable = function(options) {
+        // Group: Properties
+        this.markerRenderer = new $.jqplot.MarkerRenderer({shadow:false});
+        this.shapeRenderer = new $.jqplot.ShapeRenderer();
+        this.isDragging = false;
+        this.isOver = false;
+        this._ctx;
+        this._elem;
+        this._point;
+        this._gridData;
+        // prop: color
+        // CSS color spec for the dragged point (and adjacent line segment or bar).
+        this.color;
+        // prop: constrainTo
+        // Constrain dragging motion to an axis or to none.
+        // Allowable values are 'none', 'x', 'y'
+        this.constrainTo = 'none';  // 'x', 'y', or 'none';
+        $.extend(true, this, options);
+    };
+    
+    function DragCanvas() {
+        $.jqplot.GenericCanvas.call(this);
+        this.isDragging = false;
+        this.isOver = false;
+        this._neighbor;
+        this._cursors = [];
+    }
+    
+    DragCanvas.prototype = new $.jqplot.GenericCanvas();
+    DragCanvas.prototype.constructor = DragCanvas;
+    
+    
+    // called within scope of series
+    $.jqplot.Dragable.parseOptions = function (defaults, opts) {
+        var options = opts || {};
+        this.plugins.dragable = new $.jqplot.Dragable(options.dragable);
+        // since this function is called before series options are parsed,
+        // we can set this here and it will be overridden if needed.
+        this.isDragable = $.jqplot.config.enablePlugins;
+    };
+    
+    // called within context of plot
+    // create a canvas which we can draw on.
+    // insert it before the eventCanvas, so eventCanvas will still capture events.
+    // add a new DragCanvas object to the plot plugins to handle drawing on this new canvas.
+    $.jqplot.Dragable.postPlotDraw = function() {
+        // Memory Leaks patch    
+        if (this.plugins.dragable && this.plugins.dragable.highlightCanvas) {
+            this.plugins.dragable.highlightCanvas.resetCanvas();
+            this.plugins.dragable.highlightCanvas = null;
+        }
+
+        this.plugins.dragable = {previousCursor:'auto', isOver:false};
+        this.plugins.dragable.dragCanvas = new DragCanvas();
+        
+        this.eventCanvas._elem.before(this.plugins.dragable.dragCanvas.createElement(this._gridPadding, 'jqplot-dragable-canvas', this._plotDimensions, this));
+        var dctx = this.plugins.dragable.dragCanvas.setContext();
+    };
+    
+    //$.jqplot.preInitHooks.push($.jqplot.Dragable.init);
+    $.jqplot.preParseSeriesOptionsHooks.push($.jqplot.Dragable.parseOptions);
+    $.jqplot.postDrawHooks.push($.jqplot.Dragable.postPlotDraw);
+    $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]);
+    $.jqplot.eventListenerHooks.push(['jqplotMouseDown', handleDown]);
+    $.jqplot.eventListenerHooks.push(['jqplotMouseUp', handleUp]);
+
+    
+    function initDragPoint(plot, neighbor) {
+        var s = plot.series[neighbor.seriesIndex];
+        var drag = s.plugins.dragable;
+        
+        // first, init the mark renderer for the dragged point
+        var smr = s.markerRenderer;
+        var mr = drag.markerRenderer;
+        mr.style = smr.style;
+        mr.lineWidth = smr.lineWidth + 2.5;
+        mr.size = smr.size + 5;
+        if (!drag.color) {
+            var rgba = $.jqplot.getColorComponents(smr.color);
+            var newrgb = [rgba[0], rgba[1], rgba[2]];
+            var alpha = (rgba[3] >= 0.6) ? rgba[3]*0.6 : rgba[3]*(2-rgba[3]);
+            drag.color = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+alpha+')';
+        }
+        mr.color = drag.color;
+        mr.init();
+
+        var start = (neighbor.pointIndex > 0) ? neighbor.pointIndex - 1 : 0;
+        var end = neighbor.pointIndex+2;
+        drag._gridData = s.gridData.slice(start, end);
+    }
+    
+    function handleMove(ev, gridpos, datapos, neighbor, plot) {
+        if (plot.plugins.dragable.dragCanvas.isDragging) {
+            var dc = plot.plugins.dragable.dragCanvas;
+            var dp = dc._neighbor;
+            var s = plot.series[dp.seriesIndex];
+            var drag = s.plugins.dragable;
+            var gd = s.gridData;
+            
+            // compute the new grid position with any constraints.
+            var x = (drag.constrainTo == 'y') ? dp.gridData[0] : gridpos.x;
+            var y = (drag.constrainTo == 'x') ? dp.gridData[1] : gridpos.y;
+            
+            // compute data values for any listeners.
+            var xu = s._xaxis.series_p2u(x);
+            var yu = s._yaxis.series_p2u(y);
+            
+            // clear the canvas then redraw effect at new position.
+            var ctx = dc._ctx;
+            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+            
+            // adjust our gridData for the new mouse position
+            if (dp.pointIndex > 0) {
+                drag._gridData[1] = [x, y];
+            }
+            else {
+                drag._gridData[0] = [x, y];
+            }
+            plot.series[dp.seriesIndex].draw(dc._ctx, {gridData:drag._gridData, shadow:false, preventJqPlotSeriesDrawTrigger:true, color:drag.color, markerOptions:{color:drag.color, shadow:false}, trendline:{show:false}});
+            plot.target.trigger('jqplotSeriesPointChange', [dp.seriesIndex, dp.pointIndex, [xu,yu], [x,y]]);
+        }
+        else if (neighbor != null) {
+            var series = plot.series[neighbor.seriesIndex];
+            if (series.isDragable) {
+                var dc = plot.plugins.dragable.dragCanvas;
+                if (!dc.isOver) {
+                    dc._cursors.push(ev.target.style.cursor);
+                    ev.target.style.cursor = "pointer";
+                }
+                dc.isOver = true;
+            }
+        }
+        else if (neighbor == null) {
+            var dc = plot.plugins.dragable.dragCanvas;
+            if (dc.isOver) {
+                ev.target.style.cursor = dc._cursors.pop();
+                dc.isOver = false;
+            }
+        }
+    }
+    
+    function handleDown(ev, gridpos, datapos, neighbor, plot) {
+        var dc = plot.plugins.dragable.dragCanvas;
+        dc._cursors.push(ev.target.style.cursor);
+        if (neighbor != null) {
+            var s = plot.series[neighbor.seriesIndex];
+            var drag = s.plugins.dragable;
+            if (s.isDragable && !dc.isDragging) {
+                dc._neighbor = neighbor;
+                dc.isDragging = true;
+                initDragPoint(plot, neighbor);
+                drag.markerRenderer.draw(s.gridData[neighbor.pointIndex][0], s.gridData[neighbor.pointIndex][1], dc._ctx);
+                ev.target.style.cursor = "move";
+                plot.target.trigger('jqplotDragStart', [neighbor.seriesIndex, neighbor.pointIndex, gridpos, datapos]);
+            }
+        }
+        // Just in case of a hickup, we'll clear the drag canvas and reset.
+        else {
+           var ctx = dc._ctx;
+           ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+           dc.isDragging = false;
+        }
+    }
+    
+    function handleUp(ev, gridpos, datapos, neighbor, plot) {
+        if (plot.plugins.dragable.dragCanvas.isDragging) {
+            var dc = plot.plugins.dragable.dragCanvas;
+            // clear the canvas
+            var ctx = dc._ctx;
+            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+            dc.isDragging = false;
+            // redraw the series canvas at the new point.
+            var dp = dc._neighbor;
+            var s = plot.series[dp.seriesIndex];
+            var drag = s.plugins.dragable;
+            // compute the new grid position with any constraints.
+            var x = (drag.constrainTo == 'y') ? dp.data[0] : datapos[s.xaxis];
+            var y = (drag.constrainTo == 'x') ? dp.data[1] : datapos[s.yaxis];
+            // var x = datapos[s.xaxis];
+            // var y = datapos[s.yaxis];
+            s.data[dp.pointIndex][0] = x;
+            s.data[dp.pointIndex][1] = y;
+            plot.drawSeries({preventJqPlotSeriesDrawTrigger:true}, dp.seriesIndex);
+            dc._neighbor = null;
+            ev.target.style.cursor = dc._cursors.pop();
+            plot.target.trigger('jqplotDragStop', [gridpos, datapos]);
+        }
+    }
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.enhancedLegendRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.enhancedLegendRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.enhancedLegendRenderer.js	(revision 2119)
@@ -0,0 +1,305 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    // class $.jqplot.EnhancedLegendRenderer
+    // Legend renderer which can specify the number of rows and/or columns in the legend.
+    $.jqplot.EnhancedLegendRenderer = function(){
+        $.jqplot.TableLegendRenderer.call(this);
+    };
+    
+    $.jqplot.EnhancedLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
+    $.jqplot.EnhancedLegendRenderer.prototype.constructor = $.jqplot.EnhancedLegendRenderer;
+    
+    // called with scope of legend.
+    $.jqplot.EnhancedLegendRenderer.prototype.init = function(options) {
+        // prop: numberRows
+        // Maximum number of rows in the legend.  0 or null for unlimited.
+        this.numberRows = null;
+        // prop: numberColumns
+        // Maximum number of columns in the legend.  0 or null for unlimited.
+        this.numberColumns = null;
+        // prop: seriesToggle
+        // false to not enable series on/off toggling on the legend.
+        // true or a fadein/fadeout speed (number of milliseconds or 'fast', 'normal', 'slow') 
+        // to enable show/hide of series on click of legend item.
+        this.seriesToggle = 'normal';
+        // prop: seriesToggleReplot
+        // True to replot the chart after toggling series on/off.
+        // This will set the series show property to false.
+        // This allows for rescaling or other maniplation of chart.
+        // Set to an options object (e.g. {resetAxes: true}) for replot options.
+        this.seriesToggleReplot = false;
+        // prop: disableIEFading
+        // true to toggle series with a show/hide method only and not allow fading in/out.  
+        // This is to overcome poor performance of fade in some versions of IE.
+        this.disableIEFading = true;
+        $.extend(true, this, options);
+        
+        if (this.seriesToggle) {
+            $.jqplot.postDrawHooks.push(postDraw);
+        }
+    };
+    
+    // called with scope of legend
+    $.jqplot.EnhancedLegendRenderer.prototype.draw = function(offsets, plot) {
+        var legend = this;
+        if (this.show) {
+            var series = this._series;
+            var s;
+            var ss = 'position:absolute;';
+            ss += (this.background) ? 'background:'+this.background+';' : '';
+            ss += (this.border) ? 'border:'+this.border+';' : '';
+            ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
+            ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
+            ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
+            ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
+            ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
+            ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
+            ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
+            this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>');
+            if (this.seriesToggle) {
+                this._elem.css('z-index', '3');
+            }
+        
+            var pad = false, 
+                reverse = false,
+                nr, nc;
+            if (this.numberRows) {
+                nr = this.numberRows;
+                if (!this.numberColumns){
+                    nc = Math.ceil(series.length/nr);
+                }
+                else{
+                    nc = this.numberColumns;
+                }
+            }
+            else if (this.numberColumns) {
+                nc = this.numberColumns;
+                nr = Math.ceil(series.length/this.numberColumns);
+            }
+            else {
+                nr = series.length;
+                nc = 1;
+            }
+                
+            var i, j, tr, td1, td2, lt, rs, div, div0, div1;
+            var idx = 0;
+            // check to see if we need to reverse
+            for (i=series.length-1; i>=0; i--) {
+                if (nc == 1 && series[i]._stack || series[i].renderer.constructor == $.jqplot.BezierCurveRenderer){
+                    reverse = true;
+                }
+            }    
+                
+            for (i=0; i<nr; i++) {
+                tr = $(document.createElement('tr'));
+                tr.addClass('jqplot-table-legend');
+                if (reverse){
+                    tr.prependTo(this._elem);
+                }
+                else{
+                    tr.appendTo(this._elem);
+                }
+                for (j=0; j<nc; j++) {
+                    if (idx < series.length && (series[idx].show || series[idx].showLabel)){
+                        s = series[idx];
+                        lt = this.labels[idx] || s.label.toString();
+                        if (lt) {
+                            var color = s.color;
+                            if (!reverse){
+                                if (i>0){
+                                    pad = true;
+                                }
+                                else{
+                                    pad = false;
+                                }
+                            }
+                            else{
+                                if (i == nr -1){
+                                    pad = false;
+                                }
+                                else{
+                                    pad = true;
+                                }
+                            }
+                            rs = (pad) ? this.rowSpacing : '0';
+
+                            td1 = $(document.createElement('td'));
+                            td1.addClass('jqplot-table-legend jqplot-table-legend-swatch');
+                            td1.css({textAlign: 'center', paddingTop: rs});
+
+                            div0 = $(document.createElement('div'));
+                            div0.addClass('jqplot-table-legend-swatch-outline');
+                            div1 = $(document.createElement('div'));
+                            div1.addClass('jqplot-table-legend-swatch');
+                            div1.css({backgroundColor: color, borderColor: color});
+
+                            td1.append(div0.append(div1));
+
+                            td2 = $(document.createElement('td'));
+                            td2.addClass('jqplot-table-legend jqplot-table-legend-label');
+                            td2.css('paddingTop', rs);
+                    
+                            // td1 = $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
+                            //     '<div><div class="jqplot-table-legend-swatch" style="background-color:'+color+';border-color:'+color+';"></div>'+
+                            //     '</div></td>');
+                            // td2 = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
+                            if (this.escapeHtml){
+                                td2.text(lt);
+                            }
+                            else {
+                                td2.html(lt);
+                            }
+                            if (reverse) {
+                                if (this.showLabels) {td2.prependTo(tr);}
+                                if (this.showSwatches) {td1.prependTo(tr);}
+                            }
+                            else {
+                                if (this.showSwatches) {td1.appendTo(tr);}
+                                if (this.showLabels) {td2.appendTo(tr);}
+                            }
+                            
+                            if (this.seriesToggle) {
+
+                                // add an overlay for clicking series on/off
+                                // div0 = $(document.createElement('div'));
+                                // div0.addClass('jqplot-table-legend-overlay');
+                                // div0.css({position:'relative', left:0, top:0, height:'100%', width:'100%'});
+                                // tr.append(div0);
+
+                                var speed;
+                                if (typeof(this.seriesToggle) === 'string' || typeof(this.seriesToggle) === 'number') {
+                                    if (!$.jqplot.use_excanvas || !this.disableIEFading) {
+                                        speed = this.seriesToggle;
+                                    }
+                                } 
+                                if (this.showSwatches) {
+                                    td1.bind('click', {series:s, speed:speed, plot: plot, replot:this.seriesToggleReplot}, handleToggle);
+                                    td1.addClass('jqplot-seriesToggle');
+                                }
+                                if (this.showLabels)  {
+                                    td2.bind('click', {series:s, speed:speed, plot: plot, replot:this.seriesToggleReplot}, handleToggle);
+                                    td2.addClass('jqplot-seriesToggle');
+                                }
+
+                                // for series that are already hidden, add the hidden class
+                                if (!s.show && s.showLabel) {
+                                    td1.addClass('jqplot-series-hidden');
+                                    td2.addClass('jqplot-series-hidden');
+                                }
+                            }
+                            
+                            pad = true;
+                        }
+                    }
+                    idx++;
+                }
+                
+                td1 = td2 = div0 = div1 = null;   
+            }
+        }
+        return this._elem;
+    };
+
+    var handleToggle = function (ev) {
+        var d = ev.data,
+            s = d.series,
+            replot = d.replot,
+            plot = d.plot,
+            speed = d.speed,
+            sidx = s.index,
+            showing = false;
+
+        if (s.canvas._elem.is(':hidden') || !s.show) {
+            showing = true;
+        }
+
+        var doLegendToggle = function() {
+
+            if (replot) {
+                var opts = {};
+
+                if ($.isPlainObject(replot)) {
+                    $.extend(true, opts, replot);
+                }
+
+                plot.replot(opts);
+                // if showing, there was no canvas element to fade in, so hide here
+                // and then do a fade in.
+                if (showing && speed) {
+                    var s = plot.series[sidx];
+
+                    if (s.shadowCanvas._elem) {
+                        s.shadowCanvas._elem.hide().fadeIn(speed);
+                    }
+                    s.canvas._elem.hide().fadeIn(speed);
+                    s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).hide().fadeIn(speed);
+                }
+
+            }
+
+            else {
+                var s = plot.series[sidx];
+
+                if (s.canvas._elem.is(':hidden') || !s.show) {
+                    // Not sure if there is a better way to check for showSwatches and showLabels === true.
+                    // Test for "undefined" since default values for both showSwatches and showLables is true.
+                    if (typeof plot.options.legend.showSwatches === 'undefined' || plot.options.legend.showSwatches === true) {
+                        plot.legend._elem.find('td').eq(sidx * 2).addClass('jqplot-series-hidden');
+                    }
+                    if (typeof plot.options.legend.showLabels === 'undefined' || plot.options.legend.showLabels === true) {
+                        plot.legend._elem.find('td').eq((sidx * 2) + 1).addClass('jqplot-series-hidden');
+                    }
+                }
+                else {
+                    if (typeof plot.options.legend.showSwatches === 'undefined' || plot.options.legend.showSwatches === true) {
+                        plot.legend._elem.find('td').eq(sidx * 2).removeClass('jqplot-series-hidden');
+                    }
+                    if (typeof plot.options.legend.showLabels === 'undefined' || plot.options.legend.showLabels === true) {
+                        plot.legend._elem.find('td').eq((sidx * 2) + 1).removeClass('jqplot-series-hidden');
+                    }
+                }
+
+            }
+
+        };
+
+        s.toggleDisplay(ev, doLegendToggle);
+    };
+    
+    // called with scope of plot.
+    var postDraw = function () {
+        if (this.legend.renderer.constructor == $.jqplot.EnhancedLegendRenderer && this.legend.seriesToggle){
+            var e = this.legend._elem.detach();
+            this.eventCanvas._elem.after(e);
+        }
+    };
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.funnelRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.funnelRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.funnelRenderer.js	(revision 2119)
@@ -0,0 +1,943 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    /**
+     * Class: $.jqplot.FunnelRenderer
+     * Plugin renderer to draw a funnel chart.
+     * x values, if present, will be used as labels.
+     * y values give area size.
+     * 
+     * Funnel charts will draw a single series
+     * only.
+     * 
+     * To use this renderer, you need to include the 
+     * funnel renderer plugin, for example:
+     * 
+     * > <script type="text/javascript" src="plugins/jqplot.funnelRenderer.js"></script>
+     * 
+     * Properties described here are passed into the $.jqplot function
+     * as options on the series renderer.  For example:
+     * 
+     * > plot2 = $.jqplot('chart2', [s1, s2], {
+     * >     seriesDefaults: {
+     * >         renderer:$.jqplot.FunnelRenderer,
+     * >         rendererOptions:{
+     * >              sectionMargin: 12,
+     * >              widthRatio: 0.3
+     * >          }
+     * >      }
+     * > });
+     * 
+     * IMPORTANT
+     * 
+     * *The funnel renderer will reorder data in descending order* so the largest value in
+     * the data set is first and displayed on top of the funnel.  Data will then
+     * be displayed in descending order down the funnel.  The area of each funnel
+     * section will correspond to the value of each data point relative to the sum
+     * of all values.  That is section area is proportional to section value divided by 
+     * sum of all section values.
+     * 
+     * If your data is not in descending order when passed into the plot, *it will be
+     * reordered* when stored in the series.data property.  A copy of the unordered
+     * data is kept in the series._unorderedData property.
+     * 
+     * A funnel plot will trigger events on the plot target
+     * according to user interaction.  All events return the event object,
+     * the series index, the point (section) index, and the point data for 
+     * the appropriate section. *Note* the point index will referr to the ordered
+     * data, not the original unordered data.
+     * 
+     * 'jqplotDataMouseOver' - triggered when mousing over a section.
+     * 'jqplotDataHighlight' - triggered the first time user mouses over a section,
+     * if highlighting is enabled.
+     * 'jqplotDataUnhighlight' - triggered when a user moves the mouse out of
+     * a highlighted section.
+     * 'jqplotDataClick' - triggered when the user clicks on a section.
+     * 'jqplotDataRightClick' - tiggered when the user right clicks on a section if
+     * the "captureRightClick" option is set to true on the plot.
+     */
+    $.jqplot.FunnelRenderer = function(){
+        $.jqplot.LineRenderer.call(this);
+    };
+    
+    $.jqplot.FunnelRenderer.prototype = new $.jqplot.LineRenderer();
+    $.jqplot.FunnelRenderer.prototype.constructor = $.jqplot.FunnelRenderer;
+    
+    // called with scope of a series
+    $.jqplot.FunnelRenderer.prototype.init = function(options, plot) {
+        // Group: Properties
+        //
+        // prop: padding
+        // padding between the funnel and plot edges, legend, etc.
+        this.padding = {top: 20, right: 20, bottom: 20, left: 20};
+        // prop: sectionMargin
+        // spacing between funnel sections in pixels.
+        this.sectionMargin = 6;
+        // prop: fill
+        // true or false, whether to fill the areas.
+        this.fill = true;
+        // prop: shadowOffset
+        // offset of the shadow from the area and offset of 
+        // each succesive stroke of the shadow from the last.
+        this.shadowOffset = 2;
+        // prop: shadowAlpha
+        // transparency of the shadow (0 = transparent, 1 = opaque)
+        this.shadowAlpha = 0.07;
+        // prop: shadowDepth
+        // number of strokes to apply to the shadow, 
+        // each stroke offset shadowOffset from the last.
+        this.shadowDepth = 5;
+        // prop: highlightMouseOver
+        // True to highlight area when moused over.
+        // This must be false to enable highlightMouseDown to highlight when clicking on a area.
+        this.highlightMouseOver = true;
+        // prop: highlightMouseDown
+        // True to highlight when a mouse button is pressed over a area.
+        // This will be disabled if highlightMouseOver is true.
+        this.highlightMouseDown = false;
+        // prop: highlightColors
+        // array of colors to use when highlighting an area.
+        this.highlightColors = [];
+        // prop: widthRatio
+        // The ratio of the width of the top of the funnel to the bottom.
+        // a ratio of 0 will make an upside down pyramid. 
+        this.widthRatio = 0.2;
+        // prop: lineWidth
+        // width of line if areas are stroked and not filled.
+        this.lineWidth = 2;
+        // prop: dataLabels
+        // Either 'label', 'value', 'percent' or an array of labels to place on the pie slices.
+        // Defaults to percentage of each pie slice.
+        this.dataLabels = 'percent';
+        // prop: showDataLabels
+        // true to show data labels on slices.
+        this.showDataLabels = false;
+        // prop: dataLabelFormatString
+        // Format string for data labels.  If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage.
+        this.dataLabelFormatString = null;
+        // prop: dataLabelThreshold
+        // Threshhold in percentage (0 - 100) of pie area, below which no label will be displayed.
+        // This applies to all label types, not just to percentage labels.
+        this.dataLabelThreshold = 3;
+        this._type = 'funnel';
+        
+        this.tickRenderer = $.jqplot.FunnelTickRenderer;
+        
+        // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
+        if (options.highlightMouseDown && options.highlightMouseOver == null) {
+            options.highlightMouseOver = false;
+        }
+        
+        $.extend(true, this, options);
+        
+        // index of the currenty highlighted point, if any
+        this._highlightedPoint = null;
+        
+        // lengths of bases, or horizontal sides of areas of trapezoid.
+        this._bases = [];
+        // total area
+        this._atot;
+        // areas of segments.
+        this._areas = [];
+        // vertical lengths of segments.
+        this._lengths = [];
+        // angle of the funnel to vertical.
+        this._angle;
+        this._dataIndices = [];
+        
+        // sort data
+        this._unorderedData = $.extend(true, [], this.data);
+        var idxs = $.extend(true, [], this.data);
+        for (var i=0; i<idxs.length; i++) {
+            idxs[i].push(i);
+        }
+        this.data.sort( function (a, b) { return b[1] - a[1]; } );
+        idxs.sort( function (a, b) { return b[1] - a[1]; });
+        for (var i=0; i<idxs.length; i++) {
+            this._dataIndices.push(idxs[i][2]);
+        }
+        
+        // set highlight colors if none provided
+        if (this.highlightColors.length == 0) {
+            for (var i=0; i<this.seriesColors.length; i++){
+                var rgba = $.jqplot.getColorComponents(this.seriesColors[i]);
+                var newrgb = [rgba[0], rgba[1], rgba[2]];
+                var sum = newrgb[0] + newrgb[1] + newrgb[2];
+                for (var j=0; j<3; j++) {
+                    // when darkening, lowest color component can be is 60.
+                    newrgb[j] = (sum > 570) ?  newrgb[j] * 0.8 : newrgb[j] + 0.4 * (255 - newrgb[j]);
+                    newrgb[j] = parseInt(newrgb[j], 10);
+                }
+                this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
+            }
+        }
+
+        plot.postParseOptionsHooks.addOnce(postParseOptions);
+        plot.postInitHooks.addOnce(postInit);
+        plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
+        plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
+        plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
+        plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
+        plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
+        plot.postDrawHooks.addOnce(postPlotDraw);        
+        
+    };
+    
+    // gridData will be of form [label, percentage of total]
+    $.jqplot.FunnelRenderer.prototype.setGridData = function(plot) {
+        // set gridData property.  This will hold angle in radians of each data point.
+        var sum = 0;
+        var td = [];
+        for (var i=0; i<this.data.length; i++){
+            sum += this.data[i][1];
+            td.push([this.data[i][0], this.data[i][1]]);
+        }
+        
+        // normalize y values, so areas are proportional.
+        for (var i=0; i<td.length; i++) {
+            td[i][1] = td[i][1]/sum;
+        }
+        
+        this._bases = new Array(td.length + 1);
+        this._lengths = new Array(td.length);
+        
+        this.gridData = td;
+    };
+    
+    $.jqplot.FunnelRenderer.prototype.makeGridData = function(data, plot) {
+        // set gridData property.  This will hold angle in radians of each data point.
+        var sum = 0;
+        var td = [];
+        for (var i=0; i<this.data.length; i++){
+            sum += this.data[i][1];
+            td.push([this.data[i][0], this.data[i][1]]);
+        }
+        
+        // normalize y values, so areas are proportional.
+        for (var i=0; i<td.length; i++) {
+            td[i][1] = td[i][1]/sum;
+        }
+        
+        this._bases = new Array(td.length + 1);
+        this._lengths = new Array(td.length);
+        
+        return td;
+    };
+    
+    $.jqplot.FunnelRenderer.prototype.drawSection = function (ctx, vertices, color, isShadow) {
+        var fill = this.fill;
+        var lineWidth = this.lineWidth;
+        ctx.save();
+        
+        if (isShadow) {
+            for (var i=0; i<this.shadowDepth; i++) {
+                ctx.save();
+                ctx.translate(this.shadowOffset*Math.cos(this.shadowAngle/180*Math.PI), this.shadowOffset*Math.sin(this.shadowAngle/180*Math.PI));
+                doDraw();
+            }
+        }
+        
+        else {
+            doDraw();
+        }
+        
+        function doDraw () {
+            ctx.beginPath();  
+            ctx.fillStyle = color;
+            ctx.strokeStyle = color;
+            ctx.lineWidth = lineWidth;
+            ctx.moveTo(vertices[0][0], vertices[0][1]);
+            for (var i=1; i<4; i++) {
+                ctx.lineTo(vertices[i][0], vertices[i][1]);
+            }
+            ctx.closePath();
+            if (fill) {
+                ctx.fill();
+            }
+            else {
+                ctx.stroke();
+            }
+        }
+        
+        if (isShadow) {
+            for (var i=0; i<this.shadowDepth; i++) {
+                ctx.restore();
+            }
+        }
+        
+        ctx.restore();
+    };
+    
+    // called with scope of series
+    $.jqplot.FunnelRenderer.prototype.draw = function (ctx, gd, options, plot) {
+        var i;
+        var opts = (options != undefined) ? options : {};
+        // offset and direction of offset due to legend placement
+        var offx = 0;
+        var offy = 0;
+        var trans = 1;
+        this._areas = [];
+        // var colorGenerator = new this.colorGenerator(this.seriesColors);
+        if (options.legendInfo && options.legendInfo.placement == 'insideGrid') {
+            var li = options.legendInfo;
+            switch (li.location) {
+                case 'nw':
+                    offx = li.width + li.xoffset;
+                    break;
+                case 'w':
+                    offx = li.width + li.xoffset;
+                    break;
+                case 'sw':
+                    offx = li.width + li.xoffset;
+                    break;
+                case 'ne':
+                    offx = li.width + li.xoffset;
+                    trans = -1;
+                    break;
+                case 'e':
+                    offx = li.width + li.xoffset;
+                    trans = -1;
+                    break;
+                case 'se':
+                    offx = li.width + li.xoffset;
+                    trans = -1;
+                    break;
+                case 'n':
+                    offy = li.height + li.yoffset;
+                    break;
+                case 's':
+                    offy = li.height + li.yoffset;
+                    trans = -1;
+                    break;
+                default:
+                    break;
+            }
+        }
+        
+        var loff = (trans==1) ? this.padding.left + offx : this.padding.left;
+        var toff = (trans==1) ? this.padding.top + offy : this.padding.top;
+        var roff = (trans==-1) ? this.padding.right + offx : this.padding.right;
+        var boff = (trans==-1) ? this.padding.bottom + offy : this.padding.bottom;
+        
+        var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+        var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
+        var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+        var cw = ctx.canvas.width;
+        var ch = ctx.canvas.height;
+        this._bases[0] = cw - loff - roff;
+        var ltot = this._length = ch - toff - boff;
+
+        var hend = this._bases[0]*this.widthRatio;
+        this._atot = ltot/2 * (this._bases[0] + this._bases[0]*this.widthRatio);
+
+        this._angle = Math.atan((this._bases[0] - hend)/2/ltot);
+
+        for (i=0; i<gd.length; i++) {
+            this._areas.push(gd[i][1] * this._atot);
+        }
+
+        
+        var guess, err, count, lsum=0;
+        var tolerance = 0.0001;
+
+        for (i=0; i<this._areas.length; i++) {
+            guess = this._areas[i]/this._bases[i];
+            err = 999999;
+            this._lengths[i] = guess;
+            count = 0;
+            while (err > this._lengths[i]*tolerance && count < 100) {
+                this._lengths[i] = this._areas[i]/(this._bases[i] - this._lengths[i] * Math.tan(this._angle));
+                err = Math.abs(this._lengths[i] - guess);
+                this._bases[i+1] = this._bases[i] - (2*this._lengths[i]*Math.tan(this._angle));
+                guess = this._lengths[i];
+                count++;
+            }
+            lsum += this._lengths[i];
+        }
+        
+        // figure out vertices of each section
+        this._vertices = new Array(gd.length);
+        
+        // these are 4 coners of entire trapezoid
+        var p0 = [loff, toff],
+            p1 = [loff+this._bases[0], toff],
+            p2 = [loff + (this._bases[0] - this._bases[this._bases.length-1])/2, toff + this._length],
+            p3 = [p2[0] + this._bases[this._bases.length-1], p2[1]];
+            
+        // equations of right and left sides, returns x, y values given height of section (y value)
+        function findleft (l) {
+            var m = (p0[1] - p2[1])/(p0[0] - p2[0]);
+            var b = p0[1] - m*p0[0];
+            var y = l + p0[1];
+            
+            return [(y - b)/m, y];
+        }
+        
+        function findright (l) {
+            var m = (p1[1] - p3[1])/(p1[0] - p3[0]);
+            var b = p1[1] - m*p1[0];
+            var y = l + p1[1];
+            
+            return [(y - b)/m, y];
+        }
+        
+        var x = offx, y = offy;
+        var h=0, adj=0;
+        
+        for (i=0; i<gd.length; i++) {
+            this._vertices[i] = new Array();
+            var v = this._vertices[i];
+            var sm = this.sectionMargin;
+            if (i == 0) {
+                adj = 0;
+            }
+            if (i == 1) {
+                adj = sm/3;
+            }
+            else if (i > 0 && i < gd.length-1) {
+                adj = sm/2;
+            }
+            else if (i == gd.length -1) {
+                adj = 2*sm/3;
+            }
+            v.push(findleft(h+adj));
+            v.push(findright(h+adj));
+            h += this._lengths[i];
+            if (i == 0) {
+                adj = -2*sm/3;
+            }
+            else if (i > 0 && i < gd.length-1) {
+                adj = -sm/2;
+            }
+            else if (i == gd.length - 1) {
+                adj = 0;
+            }
+            v.push(findright(h+adj));
+            v.push(findleft(h+adj));
+            
+        }
+
+        if (this.shadow) {
+            var shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')';
+            for (var i=0; i<gd.length; i++) {
+                this.renderer.drawSection.call (this, ctx, this._vertices[i], shadowColor, true);
+            }
+            
+        }
+        for (var i=0; i<gd.length; i++) {
+            var v = this._vertices[i];
+            this.renderer.drawSection.call (this, ctx, v, this.seriesColors[i]);
+            
+            if (this.showDataLabels && gd[i][1]*100 >= this.dataLabelThreshold) {
+                var fstr, label;
+                
+                if (this.dataLabels == 'label') {
+                    fstr = this.dataLabelFormatString || '%s';
+                    label = $.jqplot.sprintf(fstr, gd[i][0]);
+                }
+                else if (this.dataLabels == 'value') {
+                    fstr = this.dataLabelFormatString || '%d';
+                    label = $.jqplot.sprintf(fstr, this.data[i][1]);
+                }
+                else if (this.dataLabels == 'percent') {
+                    fstr = this.dataLabelFormatString || '%d%%';
+                    label = $.jqplot.sprintf(fstr, gd[i][1]*100);
+                }
+                else if (this.dataLabels.constructor == Array) {
+                    fstr = this.dataLabelFormatString || '%s';
+                    label = $.jqplot.sprintf(fstr, this.dataLabels[this._dataIndices[i]]);
+                }
+                
+                var fact = (this._radius ) * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
+                
+                var x = (v[0][0] + v[1][0])/2 + this.canvas._offsets.left;
+                var y = (v[1][1] + v[2][1])/2 + this.canvas._offsets.top;
+                
+                var labelelem = $('<span class="jqplot-funnel-series jqplot-data-label" style="position:absolute;">' + label + '</span>').insertBefore(plot.eventCanvas._elem);
+                x -= labelelem.width()/2;
+                y -= labelelem.height()/2;
+                x = Math.round(x);
+                y = Math.round(y);
+                labelelem.css({left: x, top: y});
+            }
+            
+        }
+               
+    };
+    
+    $.jqplot.FunnelAxisRenderer = function() {
+        $.jqplot.LinearAxisRenderer.call(this);
+    };
+    
+    $.jqplot.FunnelAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+    $.jqplot.FunnelAxisRenderer.prototype.constructor = $.jqplot.FunnelAxisRenderer;
+        
+    
+    // There are no traditional axes on a funnel chart.  We just need to provide
+    // dummy objects with properties so the plot will render.
+    // called with scope of axis object.
+    $.jqplot.FunnelAxisRenderer.prototype.init = function(options){
+        //
+        this.tickRenderer = $.jqplot.FunnelTickRenderer;
+        $.extend(true, this, options);
+        // I don't think I'm going to need _dataBounds here.
+        // have to go Axis scaling in a way to fit chart onto plot area
+        // and provide u2p and p2u functionality for mouse cursor, etc.
+        // for convienence set _dataBounds to 0 and 100 and
+        // set min/max to 0 and 100.
+        this._dataBounds = {min:0, max:100};
+        this.min = 0;
+        this.max = 100;
+        this.showTicks = false;
+        this.ticks = [];
+        this.showMark = false;
+        this.show = false; 
+    };
+    
+    
+    
+    /**
+     * Class: $.jqplot.FunnelLegendRenderer
+     * Legend Renderer specific to funnel plots.  Set by default
+     * when the user creates a funnel plot.
+     */
+    $.jqplot.FunnelLegendRenderer = function(){
+        $.jqplot.TableLegendRenderer.call(this);
+    };
+    
+    $.jqplot.FunnelLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
+    $.jqplot.FunnelLegendRenderer.prototype.constructor = $.jqplot.FunnelLegendRenderer;
+    
+    $.jqplot.FunnelLegendRenderer.prototype.init = function(options) {
+        // Group: Properties
+        //
+        // prop: numberRows
+        // Maximum number of rows in the legend.  0 or null for unlimited.
+        this.numberRows = null;
+        // prop: numberColumns
+        // Maximum number of columns in the legend.  0 or null for unlimited.
+        this.numberColumns = null;
+        $.extend(true, this, options);
+    };
+    
+    // called with context of legend
+    $.jqplot.FunnelLegendRenderer.prototype.draw = function() {
+        var legend = this;
+        if (this.show) {
+            var series = this._series;
+            var ss = 'position:absolute;';
+            ss += (this.background) ? 'background:'+this.background+';' : '';
+            ss += (this.border) ? 'border:'+this.border+';' : '';
+            ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
+            ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
+            ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
+            ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
+            ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
+            ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
+            ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
+            this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>');
+            // Funnel charts legends don't go by number of series, but by number of data points
+            // in the series.  Refactor things here for that.
+            
+            var pad = false, 
+                reverse = false,
+                nr, nc;
+            var s = series[0];
+            var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
+            
+            if (s.show) {
+                var pd = s.data;
+                if (this.numberRows) {
+                    nr = this.numberRows;
+                    if (!this.numberColumns){
+                        nc = Math.ceil(pd.length/nr);
+                    }
+                    else{
+                        nc = this.numberColumns;
+                    }
+                }
+                else if (this.numberColumns) {
+                    nc = this.numberColumns;
+                    nr = Math.ceil(pd.length/this.numberColumns);
+                }
+                else {
+                    nr = pd.length;
+                    nc = 1;
+                }
+                
+                var i, j, tr, td1, td2, lt, rs, color;
+                var idx = 0;    
+                
+                for (i=0; i<nr; i++) {
+                    if (reverse){
+                        tr = $('<tr class="jqplot-table-legend"></tr>').prependTo(this._elem);
+                    }
+                    else{
+                        tr = $('<tr class="jqplot-table-legend"></tr>').appendTo(this._elem);
+                    }
+                    for (j=0; j<nc; j++) {
+                        if (idx < pd.length){
+                            lt = this.labels[idx] || pd[idx][0].toString();
+                            color = colorGenerator.next();
+                            if (!reverse){
+                                if (i>0){
+                                    pad = true;
+                                }
+                                else{
+                                    pad = false;
+                                }
+                            }
+                            else{
+                                if (i == nr -1){
+                                    pad = false;
+                                }
+                                else{
+                                    pad = true;
+                                }
+                            }
+                            rs = (pad) ? this.rowSpacing : '0';
+                
+                            td1 = $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
+                                '<div><div class="jqplot-table-legend-swatch" style="border-color:'+color+';"></div>'+
+                                '</div></td>');
+                            td2 = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
+                            if (this.escapeHtml){
+                                td2.text(lt);
+                            }
+                            else {
+                                td2.html(lt);
+                            }
+                            if (reverse) {
+                                td2.prependTo(tr);
+                                td1.prependTo(tr);
+                            }
+                            else {
+                                td1.appendTo(tr);
+                                td2.appendTo(tr);
+                            }
+                            pad = true;
+                        }
+                        idx++;
+                    }   
+                }
+            }
+        }
+        return this._elem;                
+    };
+    
+    // $.jqplot.FunnelLegendRenderer.prototype.pack = function(offsets) {
+    //     if (this.show) {
+    //         // fake a grid for positioning
+    //         var grid = {_top:offsets.top, _left:offsets.left, _right:offsets.right, _bottom:this._plotDimensions.height - offsets.bottom};        
+    //         if (this.placement == 'insideGrid') {
+    //             switch (this.location) {
+    //                 case 'nw':
+    //                     var a = grid._left + this.xoffset;
+    //                     var b = grid._top + this.yoffset;
+    //                     this._elem.css('left', a);
+    //                     this._elem.css('top', b);
+    //                     break;
+    //                 case 'n':
+    //                     var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+    //                     var b = grid._top + this.yoffset;
+    //                     this._elem.css('left', a);
+    //                     this._elem.css('top', b);
+    //                     break;
+    //                 case 'ne':
+    //                     var a = offsets.right + this.xoffset;
+    //                     var b = grid._top + this.yoffset;
+    //                     this._elem.css({right:a, top:b});
+    //                     break;
+    //                 case 'e':
+    //                     var a = offsets.right + this.xoffset;
+    //                     var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+    //                     this._elem.css({right:a, top:b});
+    //                     break;
+    //                 case 'se':
+    //                     var a = offsets.right + this.xoffset;
+    //                     var b = offsets.bottom + this.yoffset;
+    //                     this._elem.css({right:a, bottom:b});
+    //                     break;
+    //                 case 's':
+    //                     var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+    //                     var b = offsets.bottom + this.yoffset;
+    //                     this._elem.css({left:a, bottom:b});
+    //                     break;
+    //                 case 'sw':
+    //                     var a = grid._left + this.xoffset;
+    //                     var b = offsets.bottom + this.yoffset;
+    //                     this._elem.css({left:a, bottom:b});
+    //                     break;
+    //                 case 'w':
+    //                     var a = grid._left + this.xoffset;
+    //                     var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+    //                     this._elem.css({left:a, top:b});
+    //                     break;
+    //                 default:  // same as 'se'
+    //                     var a = grid._right - this.xoffset;
+    //                     var b = grid._bottom + this.yoffset;
+    //                     this._elem.css({right:a, bottom:b});
+    //                     break;
+    //             }
+    //             
+    //         }
+    //         else {
+    //             switch (this.location) {
+    //                 case 'nw':
+    //                     var a = this._plotDimensions.width - grid._left + this.xoffset;
+    //                     var b = grid._top + this.yoffset;
+    //                     this._elem.css('right', a);
+    //                     this._elem.css('top', b);
+    //                     break;
+    //                 case 'n':
+    //                     var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+    //                     var b = this._plotDimensions.height - grid._top + this.yoffset;
+    //                     this._elem.css('left', a);
+    //                     this._elem.css('bottom', b);
+    //                     break;
+    //                 case 'ne':
+    //                     var a = this._plotDimensions.width - offsets.right + this.xoffset;
+    //                     var b = grid._top + this.yoffset;
+    //                     this._elem.css({left:a, top:b});
+    //                     break;
+    //                 case 'e':
+    //                     var a = this._plotDimensions.width - offsets.right + this.xoffset;
+    //                     var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+    //                     this._elem.css({left:a, top:b});
+    //                     break;
+    //                 case 'se':
+    //                     var a = this._plotDimensions.width - offsets.right + this.xoffset;
+    //                     var b = offsets.bottom + this.yoffset;
+    //                     this._elem.css({left:a, bottom:b});
+    //                     break;
+    //                 case 's':
+    //                     var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+    //                     var b = this._plotDimensions.height - offsets.bottom + this.yoffset;
+    //                     this._elem.css({left:a, top:b});
+    //                     break;
+    //                 case 'sw':
+    //                     var a = this._plotDimensions.width - grid._left + this.xoffset;
+    //                     var b = offsets.bottom + this.yoffset;
+    //                     this._elem.css({right:a, bottom:b});
+    //                     break;
+    //                 case 'w':
+    //                     var a = this._plotDimensions.width - grid._left + this.xoffset;
+    //                     var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+    //                     this._elem.css({right:a, top:b});
+    //                     break;
+    //                 default:  // same as 'se'
+    //                     var a = grid._right - this.xoffset;
+    //                     var b = grid._bottom + this.yoffset;
+    //                     this._elem.css({right:a, bottom:b});
+    //                     break;
+    //             }
+    //         }
+    //     } 
+    // };
+    
+    // setup default renderers for axes and legend so user doesn't have to
+    // called with scope of plot
+    function preInit(target, data, options) {
+        options = options || {};
+        options.axesDefaults = options.axesDefaults || {};
+        options.legend = options.legend || {};
+        options.seriesDefaults = options.seriesDefaults || {};
+        // only set these if there is a funnel series
+        var setopts = false;
+        if (options.seriesDefaults.renderer == $.jqplot.FunnelRenderer) {
+            setopts = true;
+        }
+        else if (options.series) {
+            for (var i=0; i < options.series.length; i++) {
+                if (options.series[i].renderer == $.jqplot.FunnelRenderer) {
+                    setopts = true;
+                }
+            }
+        }
+        
+        if (setopts) {
+            options.axesDefaults.renderer = $.jqplot.FunnelAxisRenderer;
+            options.legend.renderer = $.jqplot.FunnelLegendRenderer;
+            options.legend.preDraw = true;
+            options.sortData = false;
+            options.seriesDefaults.pointLabels = {show: false};
+        }
+    }
+    
+    function postInit(target, data, options) {
+        // if multiple series, add a reference to the previous one so that
+        // funnel rings can nest.
+        for (var i=0; i<this.series.length; i++) {
+            if (this.series[i].renderer.constructor == $.jqplot.FunnelRenderer) {
+                // don't allow mouseover and mousedown at same time.
+                if (this.series[i].highlightMouseOver) {
+                    this.series[i].highlightMouseDown = false;
+                }
+            }
+        }
+    }
+    
+    // called with scope of plot
+    function postParseOptions(options) {
+        for (var i=0; i<this.series.length; i++) {
+            this.series[i].seriesColors = this.seriesColors;
+            this.series[i].colorGenerator = $.jqplot.colorGenerator;
+        }
+    }
+    
+    function highlight (plot, sidx, pidx) {
+        var s = plot.series[sidx];
+        var canvas = plot.plugins.funnelRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        s._highlightedPoint = pidx;
+        plot.plugins.funnelRenderer.highlightedSeriesIndex = sidx;
+        s.renderer.drawSection.call(s, canvas._ctx, s._vertices[pidx], s.highlightColors[pidx], false);
+    }
+    
+    function unhighlight (plot) {
+        var canvas = plot.plugins.funnelRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        for (var i=0; i<plot.series.length; i++) {
+            plot.series[i]._highlightedPoint = null;
+        }
+        plot.plugins.funnelRenderer.highlightedSeriesIndex = null;
+        plot.target.trigger('jqplotDataUnhighlight');
+    }
+    
+    function handleMove(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var evt1 = jQuery.Event('jqplotDataMouseOver');
+            evt1.pageX = ev.pageX;
+            evt1.pageY = ev.pageY;
+            plot.target.trigger(evt1, ins);
+            if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.funnelRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, ins[0], ins[1]);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    }
+    
+    function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.funnelRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, ins[0], ins[1]);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    }
+    
+    function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
+        var idx = plot.plugins.funnelRenderer.highlightedSeriesIndex;
+        if (idx != null && plot.series[idx].highlightMouseDown) {
+            unhighlight(plot);
+        }
+    }
+    
+    function handleClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var evt = jQuery.Event('jqplotDataClick');
+            evt.which = ev.which;
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            plot.target.trigger(evt, ins);
+        }
+    }
+    
+    function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var idx = plot.plugins.funnelRenderer.highlightedSeriesIndex;
+            if (idx != null && plot.series[idx].highlightMouseDown) {
+                unhighlight(plot);
+            }
+            var evt = jQuery.Event('jqplotDataRightClick');
+            evt.which = ev.which;
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            plot.target.trigger(evt, ins);
+        }
+    }
+    
+    // called within context of plot
+    // create a canvas which we can draw on.
+    // insert it before the eventCanvas, so eventCanvas will still capture events.
+    function postPlotDraw() {
+        // Memory Leaks patch    
+        if (this.plugins.funnelRenderer && this.plugins.funnelRenderer.highlightCanvas) {
+            this.plugins.funnelRenderer.highlightCanvas.resetCanvas();
+            this.plugins.funnelRenderer.highlightCanvas = null;
+        }
+
+        this.plugins.funnelRenderer = {};
+        this.plugins.funnelRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
+        
+        // do we have any data labels?  if so, put highlight canvas before those
+        var labels = $(this.targetId+' .jqplot-data-label');
+        if (labels.length) {
+            $(labels[0]).before(this.plugins.funnelRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-funnelRenderer-highlight-canvas', this._plotDimensions, this));
+        }
+        // else put highlight canvas before event canvas.
+        else {
+            this.eventCanvas._elem.before(this.plugins.funnelRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-funnelRenderer-highlight-canvas', this._plotDimensions, this));
+        }
+        var hctx = this.plugins.funnelRenderer.highlightCanvas.setContext();
+        this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
+    }
+    
+    $.jqplot.preInitHooks.push(preInit);
+    
+    $.jqplot.FunnelTickRenderer = function() {
+        $.jqplot.AxisTickRenderer.call(this);
+    };
+    
+    $.jqplot.FunnelTickRenderer.prototype = new $.jqplot.AxisTickRenderer();
+    $.jqplot.FunnelTickRenderer.prototype.constructor = $.jqplot.FunnelTickRenderer;
+    
+})(jQuery);
+    
+    
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.highlighter.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.highlighter.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.highlighter.js	(revision 2119)
@@ -0,0 +1,465 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]);
+    
+    /**
+     * Class: $.jqplot.Highlighter
+     * Plugin which will highlight data points when they are moused over.
+     * 
+     * To use this plugin, include the js
+     * file in your source:
+     * 
+     * > <script type="text/javascript" src="plugins/jqplot.highlighter.js"></script>
+     * 
+     * A tooltip providing information about the data point is enabled by default.
+     * To disable the tooltip, set "showTooltip" to false.
+     * 
+     * You can control what data is displayed in the tooltip with various
+     * options.  The "tooltipAxes" option controls whether the x, y or both
+     * data values are displayed.
+     * 
+     * Some chart types (e.g. hi-low-close) have more than one y value per
+     * data point. To display the additional values in the tooltip, set the
+     * "yvalues" option to the desired number of y values present (3 for a hlc chart).
+     * 
+     * By default, data values will be formatted with the same formatting
+     * specifiers as used to format the axis ticks.  A custom format code
+     * can be supplied with the tooltipFormatString option.  This will apply 
+     * to all values in the tooltip.  
+     * 
+     * For more complete control, the "formatString" option can be set.  This
+     * Allows conplete control over tooltip formatting.  Values are passed to
+     * the format string in an order determined by the "tooltipAxes" and "yvalues"
+     * options.  So, if you have a hi-low-close chart and you just want to display 
+     * the hi-low-close values in the tooltip, you could set a formatString like:
+     * 
+     * > highlighter: {
+     * >     tooltipAxes: 'y',
+     * >     yvalues: 3,
+     * >     formatString:'<table class="jqplot-highlighter">
+     * >         <tr><td>hi:</td><td>%s</td></tr>
+     * >         <tr><td>low:</td><td>%s</td></tr>
+     * >         <tr><td>close:</td><td>%s</td></tr></table>'
+     * > }
+     * 
+     */
+    $.jqplot.Highlighter = function(options) {
+        // Group: Properties
+        //
+        //prop: show
+        // true to show the highlight.
+        this.show = $.jqplot.config.enablePlugins;
+        // prop: markerRenderer
+        // Renderer used to draw the marker of the highlighted point.
+        // Renderer will assimilate attributes from the data point being highlighted,
+        // so no attributes need set on the renderer directly.
+        // Default is to turn off shadow drawing on the highlighted point.
+        this.markerRenderer = new $.jqplot.MarkerRenderer({shadow:false});
+        // prop: showMarker
+        // true to show the marker
+        this.showMarker  = true;
+        // prop: lineWidthAdjust
+        // Pixels to add to the lineWidth of the highlight.
+        this.lineWidthAdjust = 2.5;
+        // prop: sizeAdjust
+        // Pixels to add to the overall size of the highlight.
+        this.sizeAdjust = 5;
+        // prop: showTooltip
+        // Show a tooltip with data point values.
+        this.showTooltip = true;
+        // prop: tooltipLocation
+        // Where to position tooltip, 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
+        this.tooltipLocation = 'nw';
+        // prop: fadeTooltip
+        // true = fade in/out tooltip, flase = show/hide tooltip
+        this.fadeTooltip = true;
+        // prop: tooltipFadeSpeed
+        // 'slow', 'def', 'fast', or number of milliseconds.
+        this.tooltipFadeSpeed = "fast";
+        // prop: tooltipOffset
+        // Pixel offset of tooltip from the highlight.
+        this.tooltipOffset = 2;
+        // prop: tooltipAxes
+        // Which axes to display in tooltip, 'x', 'y' or 'both', 'xy' or 'yx'
+        // 'both' and 'xy' are equivalent, 'yx' reverses order of labels.
+        this.tooltipAxes = 'both';
+        // prop; tooltipSeparator
+        // String to use to separate x and y axes in tooltip.
+        this.tooltipSeparator = ', ';
+        // prop; tooltipContentEditor
+        // Function used to edit/augment/replace the formatted tooltip contents.
+        // Called as str = tooltipContentEditor(str, seriesIndex, pointIndex)
+        // where str is the generated tooltip html and seriesIndex and pointIndex identify
+        // the data point being highlighted. Should return the html for the tooltip contents.
+        this.tooltipContentEditor = null;
+        // prop: useAxesFormatters
+        // Use the x and y axes formatters to format the text in the tooltip.
+        this.useAxesFormatters = true;
+        // prop: tooltipFormatString
+        // sprintf format string for the tooltip.
+        // Uses Ash Searle's javascript sprintf implementation
+        // found here: http://hexmen.com/blog/2007/03/printf-sprintf/
+        // See http://perldoc.perl.org/functions/sprintf.html for reference.
+        // Additional "p" and "P" format specifiers added by Chris Leonello.
+        this.tooltipFormatString = '%.5P';
+        // prop: formatString
+        // alternative to tooltipFormatString
+        // will format the whole tooltip text, populating with x, y values as
+        // indicated by tooltipAxes option.  So, you could have a tooltip like:
+        // 'Date: %s, number of cats: %d' to format the whole tooltip at one go.
+        // If useAxesFormatters is true, values will be formatted according to
+        // Axes formatters and you can populate your tooltip string with 
+        // %s placeholders.
+        this.formatString = null;
+        // prop: yvalues
+        // Number of y values to expect in the data point array.
+        // Typically this is 1.  Certain plots, like OHLC, will
+        // have more y values in each data point array.
+        this.yvalues = 1;
+        // prop: bringSeriesToFront
+        // This option requires jQuery 1.4+
+        // True to bring the series of the highlighted point to the front
+        // of other series.
+        this.bringSeriesToFront = false;
+        this._tooltipElem;
+        this.isHighlighting = false;
+        this.currentNeighbor = null;
+
+        $.extend(true, this, options);
+    };
+    
+    var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
+    var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
+    var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
+    
+    // axis.renderer.tickrenderer.formatter
+    
+    // called with scope of plot
+    $.jqplot.Highlighter.init = function (target, data, opts){
+        var options = opts || {};
+        // add a highlighter attribute to the plot
+        this.plugins.highlighter = new $.jqplot.Highlighter(options.highlighter);
+    };
+    
+    // called within scope of series
+    $.jqplot.Highlighter.parseOptions = function (defaults, options) {
+        // Add a showHighlight option to the series 
+        // and set it to true by default.
+        this.showHighlight = true;
+    };
+    
+    // called within context of plot
+    // create a canvas which we can draw on.
+    // insert it before the eventCanvas, so eventCanvas will still capture events.
+    $.jqplot.Highlighter.postPlotDraw = function() {
+        // Memory Leaks patch    
+        if (this.plugins.highlighter && this.plugins.highlighter.highlightCanvas) {
+            this.plugins.highlighter.highlightCanvas.resetCanvas();
+            this.plugins.highlighter.highlightCanvas = null;
+        }
+
+        if (this.plugins.highlighter && this.plugins.highlighter._tooltipElem) {
+            this.plugins.highlighter._tooltipElem.emptyForce();
+            this.plugins.highlighter._tooltipElem = null;
+        }
+
+        this.plugins.highlighter.highlightCanvas = new $.jqplot.GenericCanvas();
+        
+        this.eventCanvas._elem.before(this.plugins.highlighter.highlightCanvas.createElement(this._gridPadding, 'jqplot-highlight-canvas', this._plotDimensions, this));
+        this.plugins.highlighter.highlightCanvas.setContext();
+
+        var elem = document.createElement('div');
+        this.plugins.highlighter._tooltipElem = $(elem);
+        elem = null;
+        this.plugins.highlighter._tooltipElem.addClass('jqplot-highlighter-tooltip');
+        this.plugins.highlighter._tooltipElem.css({position:'absolute', display:'none'});
+        
+        this.eventCanvas._elem.before(this.plugins.highlighter._tooltipElem);
+    };
+    
+    $.jqplot.preInitHooks.push($.jqplot.Highlighter.init);
+    $.jqplot.preParseSeriesOptionsHooks.push($.jqplot.Highlighter.parseOptions);
+    $.jqplot.postDrawHooks.push($.jqplot.Highlighter.postPlotDraw);
+    
+    function draw(plot, neighbor) {
+        var hl = plot.plugins.highlighter;
+        var s = plot.series[neighbor.seriesIndex];
+        var smr = s.markerRenderer;
+        var mr = hl.markerRenderer;
+        mr.style = smr.style;
+        mr.lineWidth = smr.lineWidth + hl.lineWidthAdjust;
+        mr.size = smr.size + hl.sizeAdjust;
+        var rgba = $.jqplot.getColorComponents(smr.color);
+        var newrgb = [rgba[0], rgba[1], rgba[2]];
+        var alpha = (rgba[3] >= 0.6) ? rgba[3]*0.6 : rgba[3]*(2-rgba[3]);
+        mr.color = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+alpha+')';
+        mr.init();
+        mr.draw(s.gridData[neighbor.pointIndex][0], s.gridData[neighbor.pointIndex][1], hl.highlightCanvas._ctx);
+    }
+    
+    function showTooltip(plot, series, neighbor) {
+        // neighbor looks like: {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}
+        // gridData should be x,y pixel coords on the grid.
+        // add the plot._gridPadding to that to get x,y in the target.
+        var hl = plot.plugins.highlighter;
+        var elem = hl._tooltipElem;
+        var serieshl = series.highlighter || {};
+
+        var opts = $.extend(true, {}, hl, serieshl);
+
+        if (opts.useAxesFormatters) {
+            var xf = series._xaxis._ticks[0].formatter;
+            var yf = series._yaxis._ticks[0].formatter;
+            var xfstr = series._xaxis._ticks[0].formatString;
+            var yfstr = series._yaxis._ticks[0].formatString;
+            var str;
+            var xstr = xf(xfstr, neighbor.data[0]);
+            var ystrs = [];
+            for (var i=1; i<opts.yvalues+1; i++) {
+                ystrs.push(yf(yfstr, neighbor.data[i]));
+            }
+            if (typeof opts.formatString === 'string') {
+                switch (opts.tooltipAxes) {
+                    case 'both':
+                    case 'xy':
+                        ystrs.unshift(xstr);
+                        ystrs.unshift(opts.formatString);
+                        str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
+                        break;
+                    case 'yx':
+                        ystrs.push(xstr);
+                        ystrs.unshift(opts.formatString);
+                        str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
+                        break;
+                    case 'x':
+                        str = $.jqplot.sprintf.apply($.jqplot.sprintf, [opts.formatString, xstr]);
+                        break;
+                    case 'y':
+                        ystrs.unshift(opts.formatString);
+                        str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
+                        break;
+                    default: // same as xy
+                        ystrs.unshift(xstr);
+                        ystrs.unshift(opts.formatString);
+                        str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
+                        break;
+                } 
+            }
+            else {
+                switch (opts.tooltipAxes) {
+                    case 'both':
+                    case 'xy':
+                        str = xstr;
+                        for (var i=0; i<ystrs.length; i++) {
+                            str += opts.tooltipSeparator + ystrs[i];
+                        }
+                        break;
+                    case 'yx':
+                        str = '';
+                        for (var i=0; i<ystrs.length; i++) {
+                            str += ystrs[i] + opts.tooltipSeparator;
+                        }
+                        str += xstr;
+                        break;
+                    case 'x':
+                        str = xstr;
+                        break;
+                    case 'y':
+                        str = ystrs.join(opts.tooltipSeparator);
+                        break;
+                    default: // same as 'xy'
+                        str = xstr;
+                        for (var i=0; i<ystrs.length; i++) {
+                            str += opts.tooltipSeparator + ystrs[i];
+                        }
+                        break;
+                    
+                }                
+            }
+        }
+        else {
+            var str;
+            if (typeof opts.formatString ===  'string') {
+                str = $.jqplot.sprintf.apply($.jqplot.sprintf, [opts.formatString].concat(neighbor.data));
+            }
+
+            else {
+                if (opts.tooltipAxes == 'both' || opts.tooltipAxes == 'xy') {
+                    str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]) + opts.tooltipSeparator + $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]);
+                }
+                else if (opts.tooltipAxes == 'yx') {
+                    str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]) + opts.tooltipSeparator + $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]);
+                }
+                else if (opts.tooltipAxes == 'x') {
+                    str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]);
+                }
+                else if (opts.tooltipAxes == 'y') {
+                    str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]);
+                } 
+            }
+        }
+        if ($.isFunction(opts.tooltipContentEditor)) {
+            // args str, seriesIndex, pointIndex are essential so the hook can look up
+            // extra data for the point.
+            str = opts.tooltipContentEditor(str, neighbor.seriesIndex, neighbor.pointIndex, plot);
+        }
+        elem.html(str);
+        var gridpos = {x:neighbor.gridData[0], y:neighbor.gridData[1]};
+        var ms = 0;
+        var fact = 0.707;
+        if (series.markerRenderer.show == true) { 
+            ms = (series.markerRenderer.size + opts.sizeAdjust)/2;
+        }
+
+        var loc = locations;
+        if (series.fillToZero && series.fill && neighbor.data[1] < 0) {
+          loc = oppositeLocations;
+        }
+
+        switch (loc[locationIndicies[opts.tooltipLocation]]) {
+            case 'nw':
+                var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
+                var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
+                break;
+            case 'n':
+                var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
+                var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - ms;
+                break;
+            case 'ne':
+                var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + fact * ms;
+                var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
+                break;
+            case 'e':
+                var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + ms;
+                var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
+                break;
+            case 'se':
+                var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + fact * ms;
+                var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + fact * ms;
+                break;
+            case 's':
+                var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
+                var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + ms;
+                break;
+            case 'sw':
+                var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
+                var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + fact * ms;
+                break;
+            case 'w':
+                var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - ms;
+                var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
+                break;
+            default: // same as 'nw'
+                var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
+                var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
+                break;
+        }
+        elem.css('left', x);
+        elem.css('top', y);
+        if (opts.fadeTooltip) {
+            // Fix for stacked up animations.  Thnanks Trevor!
+            elem.stop(true,true).fadeIn(opts.tooltipFadeSpeed);
+        }
+        else {
+            elem.show();
+        }
+        elem = null;
+        
+    }
+    
+    function handleMove(ev, gridpos, datapos, neighbor, plot) {
+        var hl = plot.plugins.highlighter;
+        var c = plot.plugins.cursor;
+        if (hl.show) {
+            if (neighbor == null && hl.isHighlighting) {
+                var evt = jQuery.Event('jqplotHighlighterUnhighlight');
+                plot.target.trigger(evt);
+
+                var ctx = hl.highlightCanvas._ctx;
+                ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+                if (hl.fadeTooltip) {
+                    hl._tooltipElem.fadeOut(hl.tooltipFadeSpeed);
+                }
+                else {
+                    hl._tooltipElem.hide();
+                }
+                if (hl.bringSeriesToFront) {
+                    plot.restorePreviousSeriesOrder();
+                }
+                hl.isHighlighting = false;
+                hl.currentNeighbor = null;
+                ctx = null;
+            }
+            else if (neighbor != null && plot.series[neighbor.seriesIndex].showHighlight && !hl.isHighlighting) {
+                var evt = jQuery.Event('jqplotHighlighterHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data, plot];
+                plot.target.trigger(evt, ins);
+
+                hl.isHighlighting = true;
+                hl.currentNeighbor = neighbor;
+                if (hl.showMarker) {
+                    draw(plot, neighbor);
+                }
+                if (plot.series[neighbor.seriesIndex].show && hl.showTooltip && (!c || !c._zoom.started)) {
+                    showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor);
+                }
+                if (hl.bringSeriesToFront) {
+                    plot.moveSeriesToFront(neighbor.seriesIndex);
+                }
+            }
+            // check to see if we're highlighting the wrong point.
+            else if (neighbor != null && hl.isHighlighting && hl.currentNeighbor != neighbor) {
+                // highlighting the wrong point.
+
+                // if new series allows highlighting, highlight new point.
+                if (plot.series[neighbor.seriesIndex].showHighlight) {
+                    var ctx = hl.highlightCanvas._ctx;
+                    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+                    hl.isHighlighting = true;
+                    hl.currentNeighbor = neighbor;
+                    if (hl.showMarker) {
+                        draw(plot, neighbor);
+                    }
+                    if (plot.series[neighbor.seriesIndex].show && hl.showTooltip && (!c || !c._zoom.started)) {
+                        showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor);
+                    }
+                    if (hl.bringSeriesToFront) {
+                        plot.moveSeriesToFront(neighbor.seriesIndex);
+                    }                    
+                }                
+            }
+        }
+    }
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.json2.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.json2.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.json2.js	(revision 2119)
@@ -0,0 +1,475 @@
+/*
+    2010-11-01 Chris Leonello
+    
+    Slightly modified version of the original json2.js to put JSON
+    functions under the $.jqplot namespace.
+    
+    licensing and orignal comments follow:
+    
+    http://www.JSON.org/json2.js
+    2010-08-25
+    
+    Public Domain.
+
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+    See http://www.JSON.org/js.html
+
+
+    This code should be minified before deployment.
+    See http://javascript.crockford.com/jsmin.html
+
+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+    NOT CONTROL.
+
+
+    This file creates a global JSON object containing two methods: stringify
+    and parse.
+
+        $.jqplot.JSON.stringify(value, replacer, space)
+            value       any JavaScript value, usually an object or array.
+
+            replacer    an optional parameter that determines how object
+                        values are stringified for objects. It can be a
+                        function or an array of strings.
+
+            space       an optional parameter that specifies the indentation
+                        of nested structures. If it is omitted, the text will
+                        be packed without extra whitespace. If it is a number,
+                        it will specify the number of spaces to indent at each
+                        level. If it is a string (such as '\t' or '&nbsp;'),
+                        it contains the characters used to indent at each level.
+
+            This method produces a JSON text from a JavaScript value.
+
+            When an object value is found, if the object contains a toJSON
+            method, its toJSON method will be called and the result will be
+            stringified. A toJSON method does not serialize: it returns the
+            value represented by the name/value pair that should be serialized,
+            or undefined if nothing should be serialized. The toJSON method
+            will be passed the key associated with the value, and this will be
+            bound to the value
+
+            For example, this would serialize Dates as ISO strings.
+
+                Date.prototype.toJSON = function (key) {
+                    function f(n) {
+                        // Format integers to have at least two digits.
+                        return n < 10 ? '0' + n : n;
+                    }
+
+                    return this.getUTCFullYear()   + '-' +
+                         f(this.getUTCMonth() + 1) + '-' +
+                         f(this.getUTCDate())      + 'T' +
+                         f(this.getUTCHours())     + ':' +
+                         f(this.getUTCMinutes())   + ':' +
+                         f(this.getUTCSeconds())   + 'Z';
+                };
+
+            You can provide an optional replacer method. It will be passed the
+            key and value of each member, with this bound to the containing
+            object. The value that is returned from your method will be
+            serialized. If your method returns undefined, then the member will
+            be excluded from the serialization.
+
+            If the replacer parameter is an array of strings, then it will be
+            used to select the members to be serialized. It filters the results
+            such that only members with keys listed in the replacer array are
+            stringified.
+
+            Values that do not have JSON representations, such as undefined or
+            functions, will not be serialized. Such values in objects will be
+            dropped; in arrays they will be replaced with null. You can use
+            a replacer function to replace those with JSON values.
+            $.jqplot.JSON.stringify(undefined) returns undefined.
+
+            The optional space parameter produces a stringification of the
+            value that is filled with line breaks and indentation to make it
+            easier to read.
+
+            If the space parameter is a non-empty string, then that string will
+            be used for indentation. If the space parameter is a number, then
+            the indentation will be that many spaces.
+
+            Example:
+
+            text = $.jqplot.JSON.stringify(['e', {pluribus: 'unum'}]);
+            // text is '["e",{"pluribus":"unum"}]'
+
+
+            text = $.jqplot.JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+            text = $.jqplot.JSON.stringify([new Date()], function (key, value) {
+                return this[key] instanceof Date ?
+                    'Date(' + this[key] + ')' : value;
+            });
+            // text is '["Date(---current time---)"]'
+
+
+        $.jqplot.JSON.parse(text, reviver)
+            This method parses a JSON text to produce an object or array.
+            It can throw a SyntaxError exception.
+
+            The optional reviver parameter is a function that can filter and
+            transform the results. It receives each of the keys and values,
+            and its return value is used instead of the original value.
+            If it returns what it received, then the structure is not modified.
+            If it returns undefined then the member is deleted.
+
+            Example:
+
+            // Parse the text. Values that look like ISO date strings will
+            // be converted to Date objects.
+
+            myData = $.jqplot.JSON.parse(text, function (key, value) {
+                var a;
+                if (typeof value === 'string') {
+                    a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+                    if (a) {
+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+                            +a[5], +a[6]));
+                    }
+                }
+                return value;
+            });
+
+            myData = $.jqplot.JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+                var d;
+                if (typeof value === 'string' &&
+                        value.slice(0, 5) === 'Date(' &&
+                        value.slice(-1) === ')') {
+                    d = new Date(value.slice(5, -1));
+                    if (d) {
+                        return d;
+                    }
+                }
+                return value;
+            });
+
+
+    This is a reference implementation. You are free to copy, modify, or
+    redistribute.
+*/
+
+(function($) {
+
+    $.jqplot.JSON = window.JSON;
+
+    if (!window.JSON) {
+        $.jqplot.JSON = {};
+    }
+    
+    function f(n) {
+        // Format integers to have at least two digits.
+        return n < 10 ? '0' + n : n;
+    }
+
+    if (typeof Date.prototype.toJSON !== 'function') {
+
+        Date.prototype.toJSON = function (key) {
+
+            return isFinite(this.valueOf()) ?
+                   this.getUTCFullYear()   + '-' +
+                 f(this.getUTCMonth() + 1) + '-' +
+                 f(this.getUTCDate())      + 'T' +
+                 f(this.getUTCHours())     + ':' +
+                 f(this.getUTCMinutes())   + ':' +
+                 f(this.getUTCSeconds())   + 'Z' : null;
+        };
+
+        String.prototype.toJSON =
+        Number.prototype.toJSON =
+        Boolean.prototype.toJSON = function (key) {
+            return this.valueOf();
+        };
+    }
+
+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        gap,
+        indent,
+        meta = {    // table of character substitutions
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '\\': '\\\\'
+        },
+        rep;
+
+
+    function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+        escapable.lastIndex = 0;
+        return escapable.test(string) ?
+            '"' + string.replace(escapable, function (a) {
+                var c = meta[a];
+                return typeof c === 'string' ? c :
+                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+            }) + '"' :
+            '"' + string + '"';
+    }
+
+
+    function str(key, holder) {
+
+// Produce a string from holder[key].
+
+        var i,          // The loop counter.
+            k,          // The member key.
+            v,          // The member value.
+            length,
+            mind = gap,
+            partial,
+            value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+        if (value && typeof value === 'object' &&
+                typeof value.toJSON === 'function') {
+            value = value.toJSON(key);
+        }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+        if (typeof rep === 'function') {
+            value = rep.call(holder, key, value);
+        }
+
+// What happens next depends on the value's type.
+
+        switch (typeof value) {
+        case 'string':
+            return quote(value);
+
+        case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+            return isFinite(value) ? String(value) : 'null';
+
+        case 'boolean':
+        case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+            return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+        case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+            if (!value) {
+                return 'null';
+            }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+            gap += indent;
+            partial = [];
+
+// Is the value an array?
+
+            if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+                length = value.length;
+                for (i = 0; i < length; i += 1) {
+                    partial[i] = str(i, value) || 'null';
+                }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+                v = partial.length === 0 ? '[]' :
+                    gap ? '[\n' + gap +
+                            partial.join(',\n' + gap) + '\n' +
+                                mind + ']' :
+                          '[' + partial.join(',') + ']';
+                gap = mind;
+                return v;
+            }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+            if (rep && typeof rep === 'object') {
+                length = rep.length;
+                for (i = 0; i < length; i += 1) {
+                    k = rep[i];
+                    if (typeof k === 'string') {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+                for (k in value) {
+                    if (Object.hasOwnProperty.call(value, k)) {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+            v = partial.length === 0 ? '{}' :
+                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+                        mind + '}' : '{' + partial.join(',') + '}';
+            gap = mind;
+            return v;
+        }
+    }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+    if (typeof $.jqplot.JSON.stringify !== 'function') {
+        $.jqplot.JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+            var i;
+            gap = '';
+            indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+            if (typeof space === 'number') {
+                for (i = 0; i < space; i += 1) {
+                    indent += ' ';
+                }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+            } else if (typeof space === 'string') {
+                indent = space;
+            }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+            rep = replacer;
+            if (replacer && typeof replacer !== 'function' &&
+                    (typeof replacer !== 'object' ||
+                     typeof replacer.length !== 'number')) {
+                throw new Error('$.jqplot.JSON.stringify');
+            }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+            return str('', {'': value});
+        };
+    }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+    if (typeof $.jqplot.JSON.parse !== 'function') {
+        $.jqplot.JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+            var j;
+
+            function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+                var k, v, value = holder[key];
+                if (value && typeof value === 'object') {
+                    for (k in value) {
+                        if (Object.hasOwnProperty.call(value, k)) {
+                            v = walk(value, k);
+                            if (v !== undefined) {
+                                value[k] = v;
+                            } else {
+                                delete value[k];
+                            }
+                        }
+                    }
+                }
+                return reviver.call(holder, key, value);
+            }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+            text = String(text);
+            cx.lastIndex = 0;
+            if (cx.test(text)) {
+                text = text.replace(cx, function (a) {
+                    return '\\u' +
+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+                });
+            }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+            if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+                j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+                return typeof reviver === 'function' ?
+                    walk({'': j}, '') : j;
+            }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+            throw new SyntaxError('$.jqplot.JSON.parse');
+        };
+    }
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.logAxisRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.logAxisRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.logAxisRenderer.js	(revision 2119)
@@ -0,0 +1,534 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    /**
+    *  class: $.jqplot.LogAxisRenderer
+    *  A plugin for a jqPlot to render a logarithmic axis.
+    * 
+    *  To use this renderer, include the plugin in your source
+    *  > <script type="text/javascript" language="javascript" src="plugins/jqplot.logAxisRenderer.js"></script>
+    *  
+    *  and supply the appropriate options to your plot
+    *  
+    *  > {axes:{xaxis:{renderer:$.jqplot.LogAxisRenderer}}}
+    **/ 
+    $.jqplot.LogAxisRenderer = function() {
+        $.jqplot.LinearAxisRenderer.call(this);
+        // prop: axisDefaults
+        // Default properties which will be applied directly to the series.
+        //
+        // Group: Properties
+        //
+        // Properties
+        //
+        // base - the logarithmic base, commonly 2, 10 or Math.E
+        // tickDistribution - Deprecated.  "power" distribution of ticks
+        // always used.  Option has no effect.
+        this.axisDefaults = {
+            base : 10,
+            tickDistribution :'power'
+        };
+    };
+    
+    $.jqplot.LogAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+    $.jqplot.LogAxisRenderer.prototype.constructor = $.jqplot.LogAxisRenderer;
+    
+    $.jqplot.LogAxisRenderer.prototype.init = function(options) {
+        // prop: drawBaseline
+        // True to draw the axis baseline.
+        this.drawBaseline = true;
+        // prop: minorTicks
+        // Number of ticks to add between "major" ticks.
+        // Major ticks are ticks supplied by user or auto computed.
+        // Minor ticks cannot be created by user.
+        this.minorTicks = 'auto';
+        this._scalefact = 1.0;
+
+        $.extend(true, this, options);
+
+        this._autoFormatString = '%d';
+        this._overrideFormatString = false;
+
+        for (var d in this.renderer.axisDefaults) {
+            if (this[d] == null) {
+                this[d] = this.renderer.axisDefaults[d];
+            }
+        }
+
+        this.resetDataBounds();
+    };
+    
+    $.jqplot.LogAxisRenderer.prototype.createTicks = function(plot) {
+        // we're are operating on an axis here
+        var ticks = this._ticks;
+        var userTicks = this.ticks;
+        var name = this.name;
+        var db = this._dataBounds;
+        var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
+        var interval;
+        var min, max;
+        var pos1, pos2;
+        var tt, i;
+
+        var threshold = 30;
+        // For some reason scalefactor is screwing up ticks.
+        this._scalefact =  (Math.max(dim, threshold+1) - threshold)/300;
+
+        // if we already have ticks, use them.
+        // ticks must be in order of increasing value.
+        if (userTicks.length) {
+            // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
+            for (i=0; i<userTicks.length; i++){
+                var ut = userTicks[i];
+                var t = new this.tickRenderer(this.tickOptions);
+                if (ut.constructor == Array) {
+                    t.value = ut[0];
+                    t.label = ut[1];
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    t.setTick(ut[0], this.name);
+                    this._ticks.push(t);
+                }
+
+                else if ($.isPlainObject(ut)) {
+                    $.extend(true, t, ut);
+                    t.axis = this.name;
+                    this._ticks.push(t);
+                }
+                
+                else {
+                    t.value = ut;
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    t.setTick(ut, this.name);
+                    this._ticks.push(t);
+                }
+            }
+            this.numberTicks = userTicks.length;
+            this.min = this._ticks[0].value;
+            this.max = this._ticks[this.numberTicks-1].value;
+        }
+        
+        // we don't have any ticks yet, let's make some!
+        else if (this.min == null && this.max == null) {
+            min = db.min * (2 - this.padMin);
+            max = db.max * this.padMax;
+            
+            // if min and max are same, space them out a bit
+            if (min == max) {
+                var adj = 0.05;
+                min = min*(1-adj);
+                max = max*(1+adj);
+            }
+            
+            // perform some checks
+            if (this.min != null && this.min <= 0) {
+                throw new Error("Log axis minimum must be greater than 0");
+            }
+            if (this.max != null && this.max <= 0) {
+                throw new Error("Log axis maximum must be greater than 0");
+            }
+
+            function findCeil (val) {
+                var order = Math.pow(10, Math.floor(Math.log(val)/Math.LN10));
+                return Math.ceil(val/order) * order;
+            }
+
+            function findFloor(val) {
+                var order = Math.pow(10, Math.floor(Math.log(val)/Math.LN10));
+                return Math.floor(val/order) * order;
+            }
+
+            // var range = max - min;
+            var rmin, rmax;
+
+            // for power distribution, open up range to get a nice power of axis.renderer.base.
+            // power distribution won't respect the user's min/max settings.
+            rmin = Math.pow(this.base, Math.floor(Math.log(min)/Math.log(this.base)));
+            rmax = Math.pow(this.base, Math.ceil(Math.log(max)/Math.log(this.base)));
+
+            // // if min and max are same, space them out a bit
+            // if (rmin === rmax) {
+            //     var adj = 0.05;
+            //     rmin = rmin*(1-adj);
+            //     rmax = rmax*(1+adj);
+            // }
+
+            // Handle case where a data value was zero
+            if (rmin === 0) {
+              rmin = 1;
+            }
+
+            var order = Math.round(Math.log(rmin)/Math.LN10);
+
+            if (this.tickOptions == null || !this.tickOptions.formatString) {
+                this._overrideFormatString = true;
+            }
+
+            this.min = rmin;
+            this.max = rmax;
+            var range = this.max - this.min;            
+
+            var minorTicks = (this.minorTicks === 'auto') ? 0 : this.minorTicks;
+            var numberTicks;
+            if (this.numberTicks == null){
+                if (dim > 140) {
+                    numberTicks = Math.round(Math.log(this.max/this.min)/Math.log(this.base) + 1);
+                    if (numberTicks < 2) {
+                        numberTicks = 2;
+                    }
+                    if (minorTicks === 0) {
+                        var temp = dim/(numberTicks - 1);
+                        if (temp < 100) {
+                            minorTicks = 0;
+                        }
+                        else if (temp < 190) {
+                            minorTicks = 1;
+                        }
+                        else if (temp < 250) {
+                            minorTicks = 3;
+                        }
+                        else if (temp < 600) {
+                            minorTicks = 4;
+                        }
+                        else {
+                            minorTicks = 9;
+                        }
+                    }
+                }
+                else {
+                    numberTicks = 2;
+                    if (minorTicks === 0) {
+                        minorTicks = 1;
+                    }
+                    minorTicks = 0;
+                }
+            }
+            else {
+                numberTicks = this.numberTicks;
+            }
+
+            if (order >= 0 && minorTicks !== 3) {
+                this._autoFormatString = '%d';
+            }
+            // Adjust format string for case with 3 ticks where we'll have like 1, 2.5, 5, 7.5, 10
+            else if (order <= 0 && minorTicks === 3) {
+                var temp = -(order - 1);
+                this._autoFormatString = '%.'+ Math.abs(order-1) + 'f';
+            }
+
+            // Adjust format string for values less than 1.
+            else if (order < 0) {
+                var temp = -order;
+                this._autoFormatString = '%.'+ Math.abs(order) + 'f';
+            }
+
+            else {
+                this._autoFormatString = '%d';
+            }
+
+            var to, t, val, tt1, spread, interval;
+            for (var i=0; i<numberTicks; i++){
+                tt = Math.pow(this.base, i - numberTicks + 1) * this.max;
+
+                t = new this.tickRenderer(this.tickOptions);
+            
+                if (this._overrideFormatString) {
+                    t.formatString = this._autoFormatString;
+                }
+                
+                if (!this.showTicks) {
+                    t.showLabel = false;
+                    t.showMark = false;
+                }
+                else if (!this.showTickMarks) {
+                    t.showMark = false;
+                }
+                t.setTick(tt, this.name);
+                this._ticks.push(t);
+
+                if (minorTicks && i<numberTicks-1) {
+                    tt1 = Math.pow(this.base, i - numberTicks + 2) * this.max;
+                    spread = tt1 - tt;
+                    interval = tt1 / (minorTicks+1);
+                    for (var j=minorTicks-1; j>=0; j--) {
+                        val = tt1-interval*(j+1);
+                        t = new this.tickRenderer(this.tickOptions);
+            
+                        if (this._overrideFormatString && this._autoFormatString != '') {
+                            t.formatString = this._autoFormatString;
+                        }
+                        if (!this.showTicks) {
+                            t.showLabel = false;
+                            t.showMark = false;
+                        }
+                        else if (!this.showTickMarks) {
+                            t.showMark = false;
+                        }
+                        t.setTick(val, this.name);
+                        this._ticks.push(t);
+                    }
+                }       
+            }     
+        }
+
+        // min and max are set as would be the case with zooming
+        else if (this.min != null && this.max != null) {
+            var opts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
+            var nt, ti;
+            // don't have an interval yet, pick one that gives the most
+            // "round" ticks we can get.
+            if (this.numberTicks == null && this.tickInterval == null) {
+                // var threshold = 30;
+                var tdim = Math.max(dim, threshold+1);
+                var nttarget =  Math.ceil((tdim-threshold)/35 + 1);
+
+                var ret = $.jqplot.LinearTickGenerator.bestConstrainedInterval(this.min, this.max, nttarget);
+
+                this._autoFormatString = ret[3];
+                nt = ret[2];
+                ti = ret[4];
+
+                for (var i=0; i<nt; i++) {
+                    opts.value = this.min + i * ti;
+                    t = new this.tickRenderer(opts);
+                    
+                    if (this._overrideFormatString && this._autoFormatString != '') {
+                        t.formatString = this._autoFormatString;
+                    }
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    this._ticks.push(t);
+                }
+            }
+
+            // for loose zoom, number ticks and interval are also set.
+            else if (this.numberTicks != null && this.tickInterval != null) {
+                nt = this.numberTicks;
+                for (var i=0; i<nt; i++) {
+                    opts.value = this.min + i * this.tickInterval;
+                    t = new this.tickRenderer(opts);
+                    
+                    if (this._overrideFormatString && this._autoFormatString != '') {
+                        t.formatString = this._autoFormatString;
+                    }
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    this._ticks.push(t);
+                }
+            }
+        }
+    };
+    
+    $.jqplot.LogAxisRenderer.prototype.pack = function(pos, offsets) {
+        var lb = parseInt(this.base, 10);
+        var ticks = this._ticks;
+        var trans = function (v) { return Math.log(v)/Math.log(lb); };
+        var invtrans = function (v) { return Math.pow(Math.E, (Math.log(lb)*v)); };
+        var max = trans(this.max);
+        var min = trans(this.min);
+        var offmax = offsets.max;
+        var offmin = offsets.min;
+        var lshow = (this._label == null) ? false : this._label.show;
+        
+        for (var p in pos) {
+            this._elem.css(p, pos[p]);
+        }
+        
+        this._offsets = offsets;
+        // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
+        var pixellength = offmax - offmin;
+        var unitlength = max - min;
+        
+        // point to unit and unit to point conversions references to Plot DOM element top left corner.
+        this.p2u = function(p){
+            return invtrans((p - offmin) * unitlength / pixellength + min);
+        };
+        
+        this.u2p = function(u){
+            return (trans(u) - min) * pixellength / unitlength + offmin;
+        };
+        
+        if (this.name == 'xaxis' || this.name == 'x2axis'){
+            this.series_u2p = function(u){
+                return (trans(u) - min) * pixellength / unitlength;
+            };
+            this.series_p2u = function(p){
+                return invtrans(p * unitlength / pixellength + min);
+            };
+        }
+        // yaxis is max at top of canvas.
+        else {
+            this.series_u2p = function(u){
+                return (trans(u) - max) * pixellength / unitlength;
+            };
+            this.series_p2u = function(p){
+                return invtrans(p * unitlength / pixellength + max);
+            };
+        }
+        
+        if (this.show) {
+            if (this.name == 'xaxis' || this.name == 'x2axis') {
+                for (var i=0; i<ticks.length; i++) {
+                    var t = ticks[i];
+                    if (t.show && t.showLabel) {
+                        var shim;
+                        
+                        if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+                            switch (t.labelPosition) {
+                                case 'auto':
+                                    // position at end
+                                    if (t.angle < 0) {
+                                        shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    }
+                                    // position at start
+                                    else {
+                                        shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'end':
+                                    shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                                case 'start':
+                                    shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    break;
+                                case 'middle':
+                                    shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                                default:
+                                    shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                            }
+                        }
+                        else {
+                            shim = -t.getWidth()/2;
+                        }
+                        // var shim = t.getWidth()/2;
+                        var val = this.u2p(t.value) + shim + 'px';
+                        t._elem.css('left', val);
+                        t.pack();
+                    }
+                }
+                if (lshow) {
+                    var w = this._label._elem.outerWidth(true);
+                    this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
+                    if (this.name == 'xaxis') {
+                        this._label._elem.css('bottom', '0px');
+                    }
+                    else {
+                        this._label._elem.css('top', '0px');
+                    }
+                    this._label.pack();
+                }
+            }
+            else {
+                for (var i=0; i<ticks.length; i++) {
+                    var t = ticks[i];
+                    if (t.show && t.showLabel) {                        
+                        var shim;
+                        if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+                            switch (t.labelPosition) {
+                                case 'auto':
+                                    // position at end
+                                case 'end':
+                                    if (t.angle < 0) {
+                                        shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+                                    }
+                                    else {
+                                        shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'start':
+                                    if (t.angle > 0) {
+                                        shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+                                    }
+                                    else {
+                                        shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'middle':
+                                    // if (t.angle > 0) {
+                                    //     shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    // }
+                                    // else {
+                                    //     shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    // }
+                                    shim = -t.getHeight()/2;
+                                    break;
+                                default:
+                                    shim = -t.getHeight()/2;
+                                    break;
+                            }
+                        }
+                        else {
+                            shim = -t.getHeight()/2;
+                        }
+                        
+                        var val = this.u2p(t.value) + shim + 'px';
+                        t._elem.css('top', val);
+                        t.pack();
+                    }
+                }
+                if (lshow) {
+                    var h = this._label._elem.outerHeight(true);
+                    this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
+                    if (this.name == 'yaxis') {
+                        this._label._elem.css('left', '0px');
+                    }
+                    else {
+                        this._label._elem.css('right', '0px');
+                    }   
+                    this._label.pack();
+                }
+            }
+        }        
+    };
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.mekkoAxisRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.mekkoAxisRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.mekkoAxisRenderer.js	(revision 2119)
@@ -0,0 +1,611 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    // class: $.jqplot.MekkoAxisRenderer
+    // An axis renderer for a Mekko chart.
+    // Should be used with a Mekko chart where the mekkoRenderer is used on the series.
+    // Displays the Y axis as a range from 0 to 1 (0 to 100%) and the x axis with a tick
+    // for each series scaled to the sum of all the y values.
+    $.jqplot.MekkoAxisRenderer = function() {
+    };
+    
+    // called with scope of axis object.
+    $.jqplot.MekkoAxisRenderer.prototype.init = function(options){
+        // prop: tickMode
+        // How to space the ticks on the axis.
+        // 'bar' will place a tick at the width of each bar.  
+        // This is the default for the x axis.
+        // 'even' will place ticks at even intervals.  This is
+        // the default for x2 axis and y axis.  y axis cannot be changed.
+        this.tickMode;
+        // prop: barLabelRenderer
+        // renderer to use to draw labels under each bar.
+        this.barLabelRenderer = $.jqplot.AxisLabelRenderer;
+        // prop: barLabels
+        // array of labels to put under each bar.
+        this.barLabels = this.barLabels || [];
+        // prop: barLabelOptions
+        // options object to pass to the bar label renderer.
+        this.barLabelOptions = {};
+        this.tickOptions = $.extend(true, {showGridline:false}, this.tickOptions);
+        this._barLabels = [];
+        $.extend(true, this, options);
+        if (this.name == 'yaxis') {
+            this.tickOptions.formatString = this.tickOptions.formatString || "%d\%";
+        }
+        var db = this._dataBounds;
+        db.min = 0;
+        // for y axes, scale always go from 0 to 1 (0 to 100%)
+        if (this.name == 'yaxis' || this.name == 'y2axis') {
+            db.max = 100;
+            this.tickMode = 'even';
+        }
+        // For x axes, scale goes from 0 to sum of all y values.
+        else if (this.name == 'xaxis'){
+            this.tickMode = (this.tickMode == null) ? 'bar' : this.tickMode;
+            for (var i=0; i<this._series.length; i++) {
+                db.max += this._series[i]._sumy;
+            }
+        }
+        else if (this.name == 'x2axis'){
+            this.tickMode = (this.tickMode == null) ? 'even' : this.tickMode;
+            for (var i=0; i<this._series.length; i++) {
+                db.max += this._series[i]._sumy;
+            }
+        }
+    };
+    
+    // called with scope of axis
+    $.jqplot.MekkoAxisRenderer.prototype.draw = function(ctx, plot) {
+        if (this.show) {
+            // populate the axis label and value properties.
+            // createTicks is a method on the renderer, but
+            // call it within the scope of the axis.
+            this.renderer.createTicks.call(this);
+            // fill a div with axes labels in the right direction.
+            // Need to pregenerate each axis to get its bounds and
+            // position it and the labels correctly on the plot.
+            var dim=0;
+            var temp;
+            
+            var elem = document.createElement('div');
+            this._elem = $(elem);
+            this._elem.addClass('jqplot-axis jqplot-'+this.name);
+            this._elem.css('position', 'absolute');
+            elem = null;
+            
+            if (this.name == 'xaxis' || this.name == 'x2axis') {
+                this._elem.width(this._plotDimensions.width);
+            }
+            else {
+                this._elem.height(this._plotDimensions.height);
+            }
+            
+            // draw the axis label
+            // create a _label object.
+            this.labelOptions.axis = this.name;
+            this._label = new this.labelRenderer(this.labelOptions);
+            if (this._label.show) {
+                this._elem.append(this._label.draw(ctx));
+            }
+            
+            var t, tick, elem;
+            if (this.showTicks) {
+                t = this._ticks;
+                for (var i=0; i<t.length; i++) {
+                    tick = t[i];
+                    if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
+                        this._elem.append(tick.draw(ctx));
+                    }
+                }
+            }
+            
+            // draw the series labels
+            for (i=0; i<this.barLabels.length; i++) {
+                this.barLabelOptions.axis = this.name;
+                this.barLabelOptions.label = this.barLabels[i];
+                this._barLabels.push(new this.barLabelRenderer(this.barLabelOptions));
+                if (this.tickMode != 'bar') {
+                    this._barLabels[i].show = false;
+                }
+                if (this._barLabels[i].show) {
+                    var elem = this._barLabels[i].draw(ctx, plot);
+                    elem.removeClass('jqplot-'+this.name+'-label');
+                    elem.addClass('jqplot-'+this.name+'-tick');
+                    elem.addClass('jqplot-mekko-barLabel');
+                    elem.appendTo(this._elem);
+                    elem = null;
+                }   
+            }
+            
+        }
+        return this._elem;
+    };
+    
+    // called with scope of an axis
+    $.jqplot.MekkoAxisRenderer.prototype.reset = function() {
+        this.min = this._min;
+        this.max = this._max;
+        this.tickInterval = this._tickInterval;
+        this.numberTicks = this._numberTicks;
+        // this._ticks = this.__ticks;
+    };
+    
+    // called with scope of axis
+    $.jqplot.MekkoAxisRenderer.prototype.set = function() { 
+        var dim = 0;
+        var temp;
+        var w = 0;
+        var h = 0;
+        var lshow = (this._label == null) ? false : this._label.show;
+        if (this.show && this.showTicks) {
+            var t = this._ticks;
+            for (var i=0; i<t.length; i++) {
+                var tick = t[i];
+                if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
+                    if (this.name == 'xaxis' || this.name == 'x2axis') {
+                        temp = tick._elem.outerHeight(true);
+                    }
+                    else {
+                        temp = tick._elem.outerWidth(true);
+                    }
+                    if (temp > dim) {
+                        dim = temp;
+                    }
+                }
+            }
+            
+            if (lshow) {
+                w = this._label._elem.outerWidth(true);
+                h = this._label._elem.outerHeight(true); 
+            }
+            if (this.name == 'xaxis') {
+                dim = dim + h;
+                this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
+            }
+            else if (this.name == 'x2axis') {
+                dim = dim + h;
+                this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
+            }
+            else if (this.name == 'yaxis') {
+                dim = dim + w;
+                this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
+                if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+                    this._label._elem.css('width', w+'px');
+                }
+            }
+            else {
+                dim = dim + w;
+                this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
+                if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+                    this._label._elem.css('width', w+'px');
+                }
+            }
+        }  
+    };    
+    
+    // called with scope of axis
+    $.jqplot.MekkoAxisRenderer.prototype.createTicks = function() {
+        // we're are operating on an axis here
+        var ticks = this._ticks;
+        var userTicks = this.ticks;
+        var name = this.name;
+        // databounds were set on axis initialization.
+        var db = this._dataBounds;
+        var dim, interval;
+        var min, max;
+        var pos1, pos2;
+        var t, tt, i, j;
+        
+        // if we already have ticks, use them.
+        // ticks must be in order of increasing value.
+        
+        if (userTicks.length) {
+            // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
+            for (i=0; i<userTicks.length; i++){
+                var ut = userTicks[i];
+                var t = new this.tickRenderer(this.tickOptions);
+                if (ut.constructor == Array) {
+                    t.value = ut[0];
+                    t.label = ut[1];
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    t.setTick(ut[0], this.name);
+                    this._ticks.push(t);
+                }
+                
+                else {
+                    t.value = ut;
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    t.setTick(ut, this.name);
+                    this._ticks.push(t);
+                }
+            }
+            this.numberTicks = userTicks.length;
+            this.min = this._ticks[0].value;
+            this.max = this._ticks[this.numberTicks-1].value;
+            this.tickInterval = (this.max - this.min) / (this.numberTicks - 1);
+        }
+        
+        // we don't have any ticks yet, let's make some!
+        else {
+            if (name == 'xaxis' || name == 'x2axis') {
+                dim = this._plotDimensions.width;
+            }
+            else {
+                dim = this._plotDimensions.height;
+            }
+            
+            // if min, max and number of ticks specified, user can't specify interval.
+            if (this.min != null && this.max != null && this.numberTicks != null) {
+                this.tickInterval = null;
+            }
+        
+            min = (this.min != null) ? this.min : db.min;
+            max = (this.max != null) ? this.max : db.max;
+            
+            // if min and max are same, space them out a bit.+
+            if (min == max) {
+                var adj = 0.05;
+                if (min > 0) {
+                    adj = Math.max(Math.log(min)/Math.LN10, 0.05);
+                }
+                min -= adj;
+                max += adj;
+            }
+
+            var range = max - min;
+            var rmin, rmax;
+            var temp, prev, curr;
+            var ynumticks = [3,5,6,11,21];
+            
+            // yaxis divide ticks in nice intervals from 0 to 1.
+            if (this.name == 'yaxis' || this.name == 'y2axis') { 
+                this.min = 0;
+                this.max = 100; 
+                // user didn't specify number of ticks.
+                if (!this.numberTicks){
+                    if (this.tickInterval) {
+                        this.numberTicks = 3 + Math.ceil(range / this.tickInterval);
+                    }
+                    else {
+                        temp = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
+                        for (i=0; i<ynumticks.length; i++) {
+                            curr = temp/ynumticks[i];
+                            if (curr == 1) {
+                                this.numberTicks = ynumticks[i];
+                                break;
+                            }
+                            else if (curr > 1) {
+                                prev = curr;
+                                continue;
+                            }
+                            else if (curr < 1) {
+                                // was prev or is curr closer to one?
+                                if (Math.abs(prev - 1) < Math.abs(curr - 1)) {
+                                    this.numberTicks = ynumticks[i-1];
+                                    break;
+                                }
+                                else {
+                                    this.numberTicks = ynumticks[i];
+                                    break;
+                                }
+                            }
+                            else if (i == ynumticks.length -1) {
+                                this.numberTicks = ynumticks[i];
+                            }
+                        }
+                        this.tickInterval = range / (this.numberTicks - 1);
+                    }
+                }
+                
+                // user did specify number of ticks.
+                else {
+                    this.tickInterval = range / (this.numberTicks - 1);
+                }
+
+                for (var i=0; i<this.numberTicks; i++){
+                    tt = this.min + i * this.tickInterval;
+                    t = new this.tickRenderer(this.tickOptions);
+                    // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    t.setTick(tt, this.name);
+                    this._ticks.push(t);
+                }
+            }
+            
+            // for x axes, have number ot ticks equal to number of series and ticks placed
+            // at sum of y values for each series.
+            else if (this.tickMode == 'bar') {
+                this.min = 0;
+                this.numberTicks = this._series.length + 1;
+                t = new this.tickRenderer(this.tickOptions);
+                if (!this.showTicks) {
+                    t.showLabel = false;
+                    t.showMark = false;
+                }
+                else if (!this.showTickMarks) {
+                    t.showMark = false;
+                }
+                t.setTick(0, this.name);
+                this._ticks.push(t);
+                
+                temp = 0;
+
+                for (i=1; i<this.numberTicks; i++){
+                    temp += this._series[i-1]._sumy;
+                    t = new this.tickRenderer(this.tickOptions);
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    t.setTick(temp, this.name);
+                    this._ticks.push(t);
+                }
+                this.max = this.max || temp;
+                
+                // if user specified a max and it is greater than sum, add a tick
+                if (this.max > temp) {
+                     t = new this.tickRenderer(this.tickOptions);
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    t.setTick(this.max, this.name);
+                    this._ticks.push(t);
+                    
+                }
+            }
+            
+            else if (this.tickMode == 'even') {
+                this.min = 0;
+                this.max = this.max || db.max;
+                // get a desired number of ticks
+                var nt = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
+                range = this.max - this.min;
+                this.numberTicks = nt;
+                this.tickInterval = range / (this.numberTicks - 1);
+
+                for (i=0; i<this.numberTicks; i++){
+                    tt = this.min + i * this.tickInterval;
+                    t = new this.tickRenderer(this.tickOptions);
+                    // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
+                    if (!this.showTicks) {
+                        t.showLabel = false;
+                        t.showMark = false;
+                    }
+                    else if (!this.showTickMarks) {
+                        t.showMark = false;
+                    }
+                    t.setTick(tt, this.name);
+                    this._ticks.push(t);
+                }
+                
+            }
+        }
+    };
+    
+    // called with scope of axis
+    $.jqplot.MekkoAxisRenderer.prototype.pack = function(pos, offsets) {
+        var ticks = this._ticks;
+        var max = this.max;
+        var min = this.min;
+        var offmax = offsets.max;
+        var offmin = offsets.min;
+        var lshow = (this._label == null) ? false : this._label.show;
+        
+        for (var p in pos) {
+            this._elem.css(p, pos[p]);
+        }
+        
+        this._offsets = offsets;
+        // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
+        var pixellength = offmax - offmin;
+        var unitlength = max - min;
+        
+        // point to unit and unit to point conversions references to Plot DOM element top left corner.
+        this.p2u = function(p){
+            return (p - offmin) * unitlength / pixellength + min;
+        };
+        
+        this.u2p = function(u){
+            return (u - min) * pixellength / unitlength + offmin;
+        };
+                
+        if (this.name == 'xaxis' || this.name == 'x2axis'){
+            this.series_u2p = function(u){
+                return (u - min) * pixellength / unitlength;
+            };
+            this.series_p2u = function(p){
+                return p * unitlength / pixellength + min;
+            };
+        }
+        
+        else {
+            this.series_u2p = function(u){
+                return (u - max) * pixellength / unitlength;
+            };
+            this.series_p2u = function(p){
+                return p * unitlength / pixellength + max;
+            };
+        }
+        
+        if (this.show) {
+            if (this.name == 'xaxis' || this.name == 'x2axis') {
+                for (var i=0; i<ticks.length; i++) {
+                    var t = ticks[i];
+                    if (t.show && t.showLabel) {
+                        var shim;
+                        
+                        if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+                            // will need to adjust auto positioning based on which axis this is.
+                            var temp = (this.name == 'xaxis') ? 1 : -1;
+                            switch (t.labelPosition) {
+                                case 'auto':
+                                    // position at end
+                                    if (temp * t.angle < 0) {
+                                        shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    }
+                                    // position at start
+                                    else {
+                                        shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'end':
+                                    shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                                case 'start':
+                                    shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    break;
+                                case 'middle':
+                                    shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                                default:
+                                    shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                            }
+                        }
+                        else {
+                            shim = -t.getWidth()/2;
+                        }
+                        var val = this.u2p(t.value) + shim + 'px';
+                        t._elem.css('left', val);
+                        t.pack();
+                    }
+                }
+                var w;
+                if (lshow) {
+                    w = this._label._elem.outerWidth(true);
+                    this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
+                    if (this.name == 'xaxis') {
+                        this._label._elem.css('bottom', '0px');
+                    }
+                    else {
+                        this._label._elem.css('top', '0px');
+                    }
+                    this._label.pack();
+                }
+                // now show the labels under the bars.
+                var b, l, r;
+                for (var i=0; i<this.barLabels.length; i++) {
+                    b = this._barLabels[i];
+                    if (b.show) {
+                        w = b.getWidth();
+                        l = this._ticks[i].getLeft() + this._ticks[i].getWidth();
+                        r = this._ticks[i+1].getLeft();
+                        b._elem.css('left', (r+l-w)/2+'px');
+                        b._elem.css('top', this._ticks[i]._elem.css('top'));
+                        b.pack();
+                    }
+                }
+            }
+            else {
+                for (var i=0; i<ticks.length; i++) {
+                    var t = ticks[i];
+                    if (t.show && t.showLabel) {                        
+                        var shim;
+                        if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+                            // will need to adjust auto positioning based on which axis this is.
+                            var temp = (this.name == 'yaxis') ? 1 : -1;
+                            switch (t.labelPosition) {
+                                case 'auto':
+                                    // position at end
+                                case 'end':
+                                    if (temp * t.angle < 0) {
+                                        shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+                                    }
+                                    else {
+                                        shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'start':
+                                    if (t.angle > 0) {
+                                        shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+                                    }
+                                    else {
+                                        shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'middle':
+                                    shim = -t.getHeight()/2;
+                                    break;
+                                default:
+                                    shim = -t.getHeight()/2;
+                                    break;
+                            }
+                        }
+                        else {
+                            shim = -t.getHeight()/2;
+                        }
+                        
+                        var val = this.u2p(t.value) + shim + 'px';
+                        t._elem.css('top', val);
+                        t.pack();
+                    }
+                }
+                if (lshow) {
+                    var h = this._label._elem.outerHeight(true);
+                    this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
+                    if (this.name == 'yaxis') {
+                        this._label._elem.css('left', '0px');
+                    }
+                    else {
+                        this._label._elem.css('right', '0px');
+                    }   
+                    this._label.pack();
+                }
+            }
+        }
+    };
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.mekkoRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.mekkoRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.mekkoRenderer.js	(revision 2119)
@@ -0,0 +1,437 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    /**
+     * Class: $.jqplot.MekkoRenderer
+     * Draws a Mekko style chart which shows 3 dimensional data on a 2 dimensional graph.
+     * the <$.jqplot.MekkoAxisRenderer> should be used with mekko charts.  The mekko renderer
+     * overrides the default legend renderer with its own $.jqplot.MekkoLegendRenderer
+     * which allows more flexibility to specify number of rows and columns in the legend.
+     * 
+     * Data is specified per bar in the chart.  You can specify data as an array of y values, or as 
+     * an array of [label, value] pairs.  Note that labels are used only on the first series.  
+     * Labels on subsequent series are ignored:
+     * 
+     * > bar1 = [['shirts', 8],['hats', 14],['shoes', 6],['gloves', 16],['dolls', 12]];
+     * > bar2 = [15,6,9,13,6];
+     * > bar3 = [['grumpy',4],['sneezy',2],['happy',7],['sleepy',9],['doc',7]];
+     * 
+     * If you want to place labels for each bar under the axis, you use the barLabels option on 
+     * the axes.  The bar labels can be styled with the ".jqplot-mekko-barLabel" css class.
+     * 
+     * > barLabels = ['Mickey Mouse', 'Donald Duck', 'Goofy'];
+     * > axes:{xaxis:{barLabels:barLabels}}
+     * 
+     */
+    
+    
+    $.jqplot.MekkoRenderer = function(){
+        this.shapeRenderer = new $.jqplot.ShapeRenderer();
+        // prop: borderColor
+        // color of the borders between areas on the chart
+        this.borderColor = null;
+        // prop: showBorders
+        // True to draw borders lines between areas on the chart.
+        // False will draw borders lines with the same color as the area.
+        this.showBorders = true;
+    };
+    
+    // called with scope of series.
+    $.jqplot.MekkoRenderer.prototype.init = function(options, plot) {
+        this.fill = false;
+        this.fillRect = true;
+        this.strokeRect = true;
+        this.shadow = false;
+        // width of bar on x axis.
+        this._xwidth = 0;
+        this._xstart = 0;
+        $.extend(true, this.renderer, options);
+        // set the shape renderer options
+        var opts = {lineJoin:'miter', lineCap:'butt', isarc:false, fillRect:this.fillRect, strokeRect:this.strokeRect};
+        this.renderer.shapeRenderer.init(opts);
+        plot.axes.x2axis._series.push(this);
+        this._type = 'mekko';
+    };
+    
+    // Method: setGridData
+    // converts the user data values to grid coordinates and stores them
+    // in the gridData array.  Will convert user data into appropriate
+    // rectangles.
+    // Called with scope of a series.
+    $.jqplot.MekkoRenderer.prototype.setGridData = function(plot) {
+        // recalculate the grid data
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        var data = this._plotData;
+        this.gridData = [];
+        // figure out width on x axis.
+        // this._xwidth = this._sumy / plot._sumy * this.canvas.getWidth();
+        this._xwidth = xp(this._sumy) - xp(0);
+        if (this.index>0) {
+            this._xstart = plot.series[this.index-1]._xstart + plot.series[this.index-1]._xwidth;
+        }
+        var totheight = this.canvas.getHeight();
+        var sumy = 0;
+        var cury;
+        var curheight;
+        for (var i=0; i<data.length; i++) {
+            if (data[i] != null) {
+                sumy += data[i][1];
+                cury = totheight - (sumy / this._sumy * totheight);
+                curheight = data[i][1] / this._sumy * totheight;
+                this.gridData.push([this._xstart, cury, this._xwidth, curheight]);
+            }
+        }
+    };
+    
+    // Method: makeGridData
+    // converts any arbitrary data values to grid coordinates and
+    // returns them.  This method exists so that plugins can use a series'
+    // linerenderer to generate grid data points without overwriting the
+    // grid data associated with that series.
+    // Called with scope of a series.
+    $.jqplot.MekkoRenderer.prototype.makeGridData = function(data, plot) {
+        // recalculate the grid data
+        // figure out width on x axis.
+        var xp = this._xaxis.series_u2p;
+        var totheight = this.canvas.getHeight();
+        var sumy = 0;
+        var cury;
+        var curheight;
+        var gd = [];
+        for (var i=0; i<data.length; i++) {
+            if (data[i] != null) {
+                sumy += data[i][1];
+                cury = totheight - (sumy / this._sumy * totheight);
+                curheight = data[i][1] / this._sumy * totheight;
+                gd.push([this._xstart, cury, this._xwidth, curheight]);
+            }
+        }
+        return gd;
+    };
+    
+
+    // called within scope of series.
+    $.jqplot.MekkoRenderer.prototype.draw = function(ctx, gd, options) {
+        var i;
+        var opts = (options != undefined) ? options : {};
+        var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
+        var colorGenerator = new $.jqplot.ColorGenerator(this.seriesColors);
+        ctx.save();
+        if (gd.length) {
+            if (showLine) {
+                for (i=0; i<gd.length; i++){
+                    opts.fillStyle = colorGenerator.next();
+                    if (this.renderer.showBorders) {
+                        opts.strokeStyle = this.renderer.borderColor;
+                    }
+                    else {
+                        opts.strokeStyle = opts.fillStyle;
+                    }
+                    this.renderer.shapeRenderer.draw(ctx, gd[i], opts);
+                }
+            }
+        }
+        
+        ctx.restore();
+    };  
+    
+    $.jqplot.MekkoRenderer.prototype.drawShadow = function(ctx, gd, options) {
+        // This is a no-op, no shadows on mekko charts.
+    };
+    
+    /**
+     * Class: $.jqplot.MekkoLegendRenderer
+     * Legend renderer used by mekko charts with options for 
+     * controlling number or rows and columns as well as placement
+     * outside of plot area.
+     * 
+     */
+    $.jqplot.MekkoLegendRenderer = function(){
+        //
+    };
+    
+    $.jqplot.MekkoLegendRenderer.prototype.init = function(options) {
+        // prop: numberRows
+        // Maximum number of rows in the legend.  0 or null for unlimited.
+        this.numberRows = null;
+        // prop: numberColumns
+        // Maximum number of columns in the legend.  0 or null for unlimited.
+        this.numberColumns = null;
+        // this will override the placement option on the Legend object
+        this.placement = "outside";
+        $.extend(true, this, options);
+    };
+    
+    // called with scope of legend
+    $.jqplot.MekkoLegendRenderer.prototype.draw = function() {
+        var legend = this;
+        if (this.show) {
+            var series = this._series;
+            var ss = 'position:absolute;';
+            ss += (this.background) ? 'background:'+this.background+';' : '';
+            ss += (this.border) ? 'border:'+this.border+';' : '';
+            ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
+            ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
+            ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
+            this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>');
+            // Mekko charts  legends don't go by number of series, but by number of data points
+            // in the series.  Refactor things here for that.
+            
+            var pad = false, 
+                reverse = true,    // mekko charts are always stacked, so reverse
+                nr, nc;
+            var s = series[0];
+            var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
+            
+            if (s.show) {
+                var pd = s.data;
+                if (this.numberRows) {
+                    nr = this.numberRows;
+                    if (!this.numberColumns){
+                        nc = Math.ceil(pd.length/nr);
+                    }
+                    else{
+                        nc = this.numberColumns;
+                    }
+                }
+                else if (this.numberColumns) {
+                    nc = this.numberColumns;
+                    nr = Math.ceil(pd.length/this.numberColumns);
+                }
+                else {
+                    nr = pd.length;
+                    nc = 1;
+                }
+                
+                var i, j, tr, td1, td2, lt, rs, color;
+                var idx = 0;    
+                
+                for (i=0; i<nr; i++) {
+                    if (reverse){
+                        tr = $('<tr class="jqplot-table-legend"></tr>').prependTo(this._elem);
+                    }
+                    else{
+                        tr = $('<tr class="jqplot-table-legend"></tr>').appendTo(this._elem);
+                    }
+                    for (j=0; j<nc; j++) {
+                        if (idx < pd.length) {
+                            lt = this.labels[idx] || pd[idx][0].toString();
+                            color = colorGenerator.next();
+                            if (!reverse){
+                                if (i>0){
+                                    pad = true;
+                                }
+                                else{
+                                    pad = false;
+                                }
+                            }
+                            else{
+                                if (i == nr -1){
+                                    pad = false;
+                                }
+                                else{
+                                    pad = true;
+                                }
+                            }
+                            rs = (pad) ? this.rowSpacing : '0';
+                
+                            td1 = $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
+                                '<div><div class="jqplot-table-legend-swatch" style="border-color:'+color+';"></div>'+
+                                '</div></td>');
+                            td2 = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
+                            if (this.escapeHtml){
+                                td2.text(lt);
+                            }
+                            else {
+                                td2.html(lt);
+                            }
+                            if (reverse) {
+                                td2.prependTo(tr);
+                                td1.prependTo(tr);
+                            }
+                            else {
+                                td1.appendTo(tr);
+                                td2.appendTo(tr);
+                            }
+                            pad = true;
+                        }
+                        idx++;
+                    }   
+                }
+
+                tr = null;
+                td1 = null;
+                td2 = null;
+            }
+        }
+        return this._elem;
+    };
+    
+    $.jqplot.MekkoLegendRenderer.prototype.pack = function(offsets) {
+        if (this.show) {
+            // fake a grid for positioning
+            var grid = {_top:offsets.top, _left:offsets.left, _right:offsets.right, _bottom:this._plotDimensions.height - offsets.bottom};        
+            if (this.placement == 'insideGrid') {
+                switch (this.location) {
+                    case 'nw':
+                        var a = grid._left + this.xoffset;
+                        var b = grid._top + this.yoffset;
+                        this._elem.css('left', a);
+                        this._elem.css('top', b);
+                        break;
+                    case 'n':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        var b = grid._top + this.yoffset;
+                        this._elem.css('left', a);
+                        this._elem.css('top', b);
+                        break;
+                    case 'ne':
+                        var a = offsets.right + this.xoffset;
+                        var b = grid._top + this.yoffset;
+                        this._elem.css({right:a, top:b});
+                        break;
+                    case 'e':
+                        var a = offsets.right + this.xoffset;
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({right:a, top:b});
+                        break;
+                    case 'se':
+                        var a = offsets.right + this.xoffset;
+                        var b = offsets.bottom + this.yoffset;
+                        this._elem.css({right:a, bottom:b});
+                        break;
+                    case 's':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        var b = offsets.bottom + this.yoffset;
+                        this._elem.css({left:a, bottom:b});
+                        break;
+                    case 'sw':
+                        var a = grid._left + this.xoffset;
+                        var b = offsets.bottom + this.yoffset;
+                        this._elem.css({left:a, bottom:b});
+                        break;
+                    case 'w':
+                        var a = grid._left + this.xoffset;
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({left:a, top:b});
+                        break;
+                    default:  // same as 'se'
+                        var a = grid._right - this.xoffset;
+                        var b = grid._bottom + this.yoffset;
+                        this._elem.css({right:a, bottom:b});
+                        break;
+                }
+                
+            }
+            else {
+                switch (this.location) {
+                    case 'nw':
+                        var a = this._plotDimensions.width - grid._left + this.xoffset;
+                        var b = grid._top + this.yoffset;
+                        this._elem.css('right', a);
+                        this._elem.css('top', b);
+                        break;
+                    case 'n':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        var b = this._plotDimensions.height - grid._top + this.yoffset;
+                        this._elem.css('left', a);
+                        this._elem.css('bottom', b);
+                        break;
+                    case 'ne':
+                        var a = this._plotDimensions.width - offsets.right + this.xoffset;
+                        var b = grid._top + this.yoffset;
+                        this._elem.css({left:a, top:b});
+                        break;
+                    case 'e':
+                        var a = this._plotDimensions.width - offsets.right + this.xoffset;
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({left:a, top:b});
+                        break;
+                    case 'se':
+                        var a = this._plotDimensions.width - offsets.right + this.xoffset;
+                        var b = offsets.bottom + this.yoffset;
+                        this._elem.css({left:a, bottom:b});
+                        break;
+                    case 's':
+                        var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
+                        var b = this._plotDimensions.height - offsets.bottom + this.yoffset;
+                        this._elem.css({left:a, top:b});
+                        break;
+                    case 'sw':
+                        var a = this._plotDimensions.width - grid._left + this.xoffset;
+                        var b = offsets.bottom + this.yoffset;
+                        this._elem.css({right:a, bottom:b});
+                        break;
+                    case 'w':
+                        var a = this._plotDimensions.width - grid._left + this.xoffset;
+                        var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
+                        this._elem.css({right:a, top:b});
+                        break;
+                    default:  // same as 'se'
+                        var a = grid._right - this.xoffset;
+                        var b = grid._bottom + this.yoffset;
+                        this._elem.css({right:a, bottom:b});
+                        break;
+                }
+            }
+        } 
+    };
+    
+    // setup default renderers for axes and legend so user doesn't have to
+    // called with scope of plot
+    function preInit(target, data, options) {
+        options = options || {};
+        options.axesDefaults = options.axesDefaults || {};
+        options.legend = options.legend || {};
+        options.seriesDefaults = options.seriesDefaults || {};
+        var setopts = false;
+        if (options.seriesDefaults.renderer == $.jqplot.MekkoRenderer) {
+            setopts = true;
+        }
+        else if (options.series) {
+            for (var i=0; i < options.series.length; i++) {
+                if (options.series[i].renderer == $.jqplot.MekkoRenderer) {
+                    setopts = true;
+                }
+            }
+        }
+        
+        if (setopts) {
+            options.axesDefaults.renderer = $.jqplot.MekkoAxisRenderer;
+            options.legend.renderer = $.jqplot.MekkoLegendRenderer;
+            options.legend.preDraw = true;
+        }
+    }
+    
+    $.jqplot.preInitHooks.push(preInit);
+    
+})(jQuery);    
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.meterGaugeRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.meterGaugeRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.meterGaugeRenderer.js	(revision 2119)
@@ -0,0 +1,1029 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    /**
+     * Class: $.jqplot.MeterGaugeRenderer
+     * Plugin renderer to draw a meter gauge chart.
+     * 
+     * Data consists of a single series with 1 data point to position the gauge needle.
+     * 
+     * To use this renderer, you need to include the 
+     * meter gauge renderer plugin, for example:
+     * 
+     * > <script type="text/javascript" src="plugins/jqplot.meterGaugeRenderer.js"></script>
+     * 
+     * Properties described here are passed into the $.jqplot function
+     * as options on the series renderer.  For example:
+     * 
+     * > plot0 = $.jqplot('chart0',[[18]],{
+     * >     title: 'Network Speed',
+     * >     seriesDefaults: {
+     * >         renderer: $.jqplot.MeterGaugeRenderer,
+     * >         rendererOptions: {
+     * >             label: 'MB/s'
+     * >         }
+     * >     }
+     * > });
+     * 
+     * A meterGauge plot does not support events.
+     */
+    $.jqplot.MeterGaugeRenderer = function(){
+        $.jqplot.LineRenderer.call(this);
+    };
+    
+    $.jqplot.MeterGaugeRenderer.prototype = new $.jqplot.LineRenderer();
+    $.jqplot.MeterGaugeRenderer.prototype.constructor = $.jqplot.MeterGaugeRenderer;
+    
+    // called with scope of a series
+    $.jqplot.MeterGaugeRenderer.prototype.init = function(options) {
+        // Group: Properties
+        //
+        // prop: diameter
+        // Outer diameter of the meterGauge, auto computed by default
+        this.diameter = null;
+        // prop: padding
+        // padding between the meterGauge and plot edges, auto
+        // calculated by default.
+        this.padding = null;
+        // prop: shadowOffset
+        // offset of the shadow from the gauge ring and offset of 
+        // each succesive stroke of the shadow from the last.
+        this.shadowOffset = 2;
+        // prop: shadowAlpha
+        // transparency of the shadow (0 = transparent, 1 = opaque)
+        this.shadowAlpha = 0.07;
+        // prop: shadowDepth
+        // number of strokes to apply to the shadow, 
+        // each stroke offset shadowOffset from the last.
+        this.shadowDepth = 4;
+        // prop: background
+        // background color of the inside of the gauge.
+        this.background = "#efefef";
+        // prop: ringColor
+        // color of the outer ring, hub, and needle of the gauge.
+        this.ringColor = "#BBC6D0";
+        // needle color not implemented yet.
+        this.needleColor = "#C3D3E5";
+        // prop: tickColor
+        // color of the tick marks around the gauge.
+        this.tickColor = "#989898";
+        // prop: ringWidth
+        // width of the ring around the gauge.  Auto computed by default.
+        this.ringWidth = null;
+        // prop: min
+        // Minimum value on the gauge.  Auto computed by default
+        this.min;
+        // prop: max
+        // Maximum value on the gauge. Auto computed by default
+        this.max;
+        // prop: ticks
+        // Array of tick values. Auto computed by default.
+        this.ticks = [];
+        // prop: showTicks
+        // true to show ticks around gauge.
+        this.showTicks = true;
+        // prop: showTickLabels
+        // true to show tick labels next to ticks.
+        this.showTickLabels = true;
+        // prop: label
+        // A gauge label like 'kph' or 'Volts'
+        this.label = null;
+        // prop: labelHeightAdjust
+        // Number of Pixels to offset the label up (-) or down (+) from its default position.
+        this.labelHeightAdjust = 0;
+        // prop: labelPosition
+        // Where to position the label, either 'inside' or 'bottom'.
+        this.labelPosition = 'inside';
+        // prop: intervals
+        // Array of ranges to be drawn around the gauge.
+        // Array of form:
+        // > [value1, value2, ...]
+        // indicating the values for the first, second, ... intervals.
+        this.intervals = [];
+        // prop: intervalColors
+        // Array of colors to use for the intervals.
+        this.intervalColors = [ "#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"];
+        // prop: intervalInnerRadius
+        // Radius of the inner circle of the interval ring.
+        this.intervalInnerRadius =  null;
+        // prop: intervalOuterRadius
+        // Radius of the outer circle of the interval ring.
+        this.intervalOuterRadius = null;
+        this.tickRenderer = $.jqplot.MeterGaugeTickRenderer;
+        // ticks spaced every 1, 2, 2.5, 5, 10, 20, .1, .2, .25, .5, etc.
+        this.tickPositions = [1, 2, 2.5, 5, 10];
+        // prop: tickSpacing
+        // Degrees between ticks.  This is a target number, if 
+        // incompatible span and ticks are supplied, a suitable
+        // spacing close to this value will be computed.
+        this.tickSpacing = 30;
+        this.numberMinorTicks = null;
+        // prop: hubRadius
+        // Radius of the hub at the bottom center of gauge which the needle attaches to.
+        // Auto computed by default
+        this.hubRadius = null;
+        // prop: tickPadding
+        // padding of the tick marks to the outer ring and the tick labels to marks.
+        // Auto computed by default.
+        this.tickPadding = null;
+        // prop: needleThickness
+        // Maximum thickness the needle.  Auto computed by default.
+        this.needleThickness = null;
+        // prop: needlePad
+        // Padding between needle and inner edge of the ring when the needle is at the min or max gauge value.
+        this.needlePad = 6;
+        // prop: pegNeedle
+        // True will stop needle just below/above the  min/max values if data is below/above min/max,
+        // as if the meter is "pegged".
+        this.pegNeedle = true;
+        this._type = 'meterGauge';
+        
+        $.extend(true, this, options);
+        this.type = null;
+        this.numberTicks = null;
+        this.tickInterval = null;
+        // span, the sweep (in degrees) from min to max.  This gauge is 
+        // a semi-circle.
+        this.span = 180;
+        // get rid of this nonsense
+        // this.innerSpan = this.span;
+        if (this.type == 'circular') {
+            this.semiCircular = false;
+        }
+        else if (this.type != 'circular') {
+            this.semiCircular = true;
+        }
+        else {
+            this.semiCircular = (this.span <= 180) ? true : false;
+        }
+        this._tickPoints = [];
+        // reference to label element.
+        this._labelElem = null;
+        
+        // start the gauge at the beginning of the span
+        this.startAngle = (90 + (360 - this.span)/2) * Math.PI/180;
+        this.endAngle = (90 - (360 - this.span)/2) * Math.PI/180;
+        
+        this.setmin = !!(this.min == null);
+        this.setmax = !!(this.max == null);
+        
+        // if given intervals and is an array of values, create labels and colors.
+        if (this.intervals.length) {
+            if (this.intervals[0].length == null || this.intervals.length == 1) {
+                for (var i=0; i<this.intervals.length; i++) {
+                    this.intervals[i] = [this.intervals[i], this.intervals[i], this.intervalColors[i]];
+                }
+            }
+            else if (this.intervals[0].length == 2) {
+                for (i=0; i<this.intervals.length; i++) {
+                    this.intervals[i] = [this.intervals[i][0], this.intervals[i][1], this.intervalColors[i]];
+                }
+            }
+        }
+        
+        // compute min, max and ticks if not supplied:
+        if (this.ticks.length) {
+            if (this.ticks[0].length == null || this.ticks[0].length == 1) {
+                for (var i=0; i<this.ticks.length; i++) {
+                    this.ticks[i] = [this.ticks[i], this.ticks[i]];
+                }
+            }
+            this.min = (this.min == null) ? this.ticks[0][0] : this.min;
+            this.max = (this.max == null) ? this.ticks[this.ticks.length-1][0] : this.max;
+            this.setmin = false;
+            this.setmax = false;
+            this.numberTicks = this.ticks.length;
+            this.tickInterval = this.ticks[1][0] - this.ticks[0][0];
+            this.tickFactor = Math.floor(parseFloat((Math.log(this.tickInterval)/Math.log(10)).toFixed(11)));
+            // use the first interal to calculate minor ticks;
+            this.numberMinorTicks = getnmt(this.tickPositions, this.tickInterval, this.tickFactor);
+            if (!this.numberMinorTicks) {
+                this.numberMinorTicks = getnmt(this.tickPositions, this.tickInterval, this.tickFactor-1);
+            }
+            if (!this.numberMinorTicks) {
+                this.numberMinorTicks = 1;
+            }
+        }
+        
+        else if (this.intervals.length) {
+            this.min = (this.min == null) ? 0 : this.min;
+            this.setmin = false;
+            if (this.max == null) {
+                if (this.intervals[this.intervals.length-1][0] >= this.data[0][1]) {
+                    this.max = this.intervals[this.intervals.length-1][0];
+                    this.setmax = false;
+                }
+            }
+            else {
+                this.setmax = false;
+            }
+        }
+        
+        else {
+            // no ticks and no intervals supplied, put needle in middle
+            this.min = (this.min == null) ? 0 : this.min;
+            this.setmin = false;
+            if (this.max == null) {
+                this.max = this.data[0][1] * 1.25;
+                this.setmax = true;
+            }
+            else {
+                this.setmax = false;
+            }
+        }
+    };
+    
+    $.jqplot.MeterGaugeRenderer.prototype.setGridData = function(plot) {
+        // set gridData property.  This will hold angle in radians of each data point.
+        var stack = [];
+        var td = [];
+        var sa = this.startAngle;
+        for (var i=0; i<this.data.length; i++){
+            stack.push(this.data[i][1]);
+            td.push([this.data[i][0]]);
+            if (i>0) {
+                stack[i] += stack[i-1];
+            }
+        }
+        var fact = Math.PI*2/stack[stack.length - 1];
+        
+        for (var i=0; i<stack.length; i++) {
+            td[i][1] = stack[i] * fact;
+        }
+        this.gridData = td;
+    };
+    
+    $.jqplot.MeterGaugeRenderer.prototype.makeGridData = function(data, plot) {
+        var stack = [];
+        var td = [];
+        var sa = this.startAngle;
+        for (var i=0; i<data.length; i++){
+            stack.push(data[i][1]);
+            td.push([data[i][0]]);
+            if (i>0) {
+                stack[i] += stack[i-1];
+            }
+        }
+        var fact = Math.PI*2/stack[stack.length - 1];
+        
+        for (var i=0; i<stack.length; i++) {
+            td[i][1] = stack[i] * fact;
+        }
+        return td;
+    };
+
+        
+    function getnmt(pos, interval, fact) {
+        var temp;
+        for (var i=pos.length-1; i>=0; i--) {
+            temp = interval/(pos[i] * Math.pow(10, fact));
+            if (temp == 4 || temp == 5) {
+                return temp - 1;
+            }
+        }
+        return null;
+    }
+    
+    // called with scope of series
+    $.jqplot.MeterGaugeRenderer.prototype.draw = function (ctx, gd, options) {
+        var i;
+        var opts = (options != undefined) ? options : {};
+        // offset and direction of offset due to legend placement
+        var offx = 0;
+        var offy = 0;
+        var trans = 1;
+        if (options.legendInfo && options.legendInfo.placement == 'inside') {
+            var li = options.legendInfo;
+            switch (li.location) {
+                case 'nw':
+                    offx = li.width + li.xoffset;
+                    break;
+                case 'w':
+                    offx = li.width + li.xoffset;
+                    break;
+                case 'sw':
+                    offx = li.width + li.xoffset;
+                    break;
+                case 'ne':
+                    offx = li.width + li.xoffset;
+                    trans = -1;
+                    break;
+                case 'e':
+                    offx = li.width + li.xoffset;
+                    trans = -1;
+                    break;
+                case 'se':
+                    offx = li.width + li.xoffset;
+                    trans = -1;
+                    break;
+                case 'n':
+                    offy = li.height + li.yoffset;
+                    break;
+                case 's':
+                    offy = li.height + li.yoffset;
+                    trans = -1;
+                    break;
+                default:
+                    break;
+            }
+        }
+        
+        
+            
+        // pre-draw so can get its dimensions.
+        if (this.label) {
+            this._labelElem = $('<div class="jqplot-meterGauge-label" style="position:absolute;">'+this.label+'</div>');
+            this.canvas._elem.after(this._labelElem);
+        }
+        
+        var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+        var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
+        var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+        var cw = ctx.canvas.width;
+        var ch = ctx.canvas.height;
+        if (this.padding == null) {
+            this.padding = Math.round(Math.min(cw, ch)/30);
+        }
+        var w = cw - offx - 2 * this.padding;
+        var h = ch - offy - 2 * this.padding;
+        if (this.labelPosition == 'bottom' && this.label) {
+            h -= this._labelElem.outerHeight(true);
+        }
+        var mindim = Math.min(w,h);
+        var d = mindim;
+            
+        if (!this.diameter) {
+            if (this.semiCircular) {
+                if ( w >= 2*h) {
+                    if (!this.ringWidth) {
+                        this.ringWidth = 2*h/35;
+                    }
+                    this.needleThickness = this.needleThickness || 2+Math.pow(this.ringWidth, 0.8);
+                    this.innerPad = this.ringWidth/2 + this.needleThickness/2 + this.needlePad;
+                    this.diameter = 2 * (h - 2*this.innerPad);
+                }
+                else {
+                    if (!this.ringWidth) {
+                        this.ringWidth = w/35;
+                    }
+                    this.needleThickness = this.needleThickness || 2+Math.pow(this.ringWidth, 0.8);
+                    this.innerPad = this.ringWidth/2 + this.needleThickness/2 + this.needlePad;
+                    this.diameter = w - 2*this.innerPad - this.ringWidth - this.padding;
+                }
+                // center taking into account legend and over draw for gauge bottom below hub.
+                // this will be center of hub.
+                this._center = [(cw - trans * offx)/2 + trans * offx,  (ch + trans*offy - this.padding - this.ringWidth - this.innerPad)];
+            }
+            else {
+                if (!this.ringWidth) {
+                    this.ringWidth = d/35;
+                }
+                this.needleThickness = this.needleThickness || 2+Math.pow(this.ringWidth, 0.8);
+                this.innerPad = 0;
+                this.diameter = d - this.ringWidth;
+                // center in middle of canvas taking into account legend.
+                // will be center of hub.
+                this._center = [(cw-trans*offx)/2 + trans * offx, (ch-trans*offy)/2 + trans * offy];
+            }
+            if (this._labelElem && this.labelPosition == 'bottom') {
+                this._center[1] -= this._labelElem.outerHeight(true);
+            }
+            
+        }
+
+        this._radius = this.diameter/2;
+        
+        this.tickSpacing = 6000/this.diameter;
+        
+        if (!this.hubRadius) {
+            this.hubRadius = this.diameter/18;
+        }
+        
+        this.shadowOffset = 0.5 + this.ringWidth/9;
+        this.shadowWidth = this.ringWidth*1;
+        
+        this.tickPadding = 3 + Math.pow(this.diameter/20, 0.7);
+        this.tickOuterRadius = this._radius - this.ringWidth/2 - this.tickPadding;
+        this.tickLength = (this.showTicks) ? this._radius/13 : 0;
+        
+        if (this.ticks.length == 0) {
+            // no ticks, lets make some.
+            var max = this.max,
+                min = this.min,
+                setmax = this.setmax,
+                setmin = this.setmin,
+                ti = (max - min) * this.tickSpacing / this.span;
+            var tf = Math.floor(parseFloat((Math.log(ti)/Math.log(10)).toFixed(11)));
+            var tp = (ti/Math.pow(10, tf));
+            (tp > 2 && tp <= 2.5) ? tp = 2.5 : tp = Math.ceil(tp);
+            var t = this.tickPositions;
+            var tpindex, nt;
+    
+            for (i=0; i<t.length; i++) {
+                if (tp == t[i] || i && t[i-1] < tp && tp < t[i]) { 
+                    ti = t[i]*Math.pow(10, tf);
+                    tpindex = i;
+                }
+            }
+        
+            for (i=0; i<t.length; i++) {
+                if (tp == t[i] || i && t[i-1] < tp && tp < t[i]) { 
+                    ti = t[i]*Math.pow(10, tf);
+                    nt = Math.ceil((max - min) / ti);
+                }
+            }
+        
+            // both max and min are free
+            if (setmax && setmin) {
+                var tmin = (min > 0) ? min - min % ti : min - min % ti - ti;
+                if (!this.forceZero) {
+                    var diff = Math.min(min - tmin, 0.8*ti);
+                    var ntp = Math.floor(diff/t[tpindex]);
+                    if (ntp > 1) {
+                        tmin = tmin + t[tpindex] * (ntp-1);
+                        if (parseInt(tmin, 10) != tmin && parseInt(tmin-t[tpindex], 10) == tmin-t[tpindex]) {
+                            tmin = tmin - t[tpindex];
+                        }
+                    }
+                }
+                if (min == tmin) {
+                    min -= ti;
+                }
+                else {
+                    // tmin should always be lower than dataMin
+                    if (min - tmin > 0.23*ti) {
+                        min = tmin;
+                    }
+                    else {
+                        min = tmin -ti;
+                        nt += 1;
+                    }
+                }
+                nt += 1;
+                var tmax = min + (nt - 1) * ti;
+                if (max >= tmax) { 
+                    tmax += ti;
+                    nt += 1;
+                }
+                // now tmax should always be mroe than dataMax
+                if (tmax - max < 0.23*ti) { 
+                    tmax += ti;
+                    nt += 1;
+                }
+                this.max = max = tmax;
+                this.min = min;    
+
+                this.tickInterval = ti;
+                this.numberTicks = nt;
+                var it;
+                for (i=0; i<nt; i++) {
+                    it = parseFloat((min+i*ti).toFixed(11));
+                    this.ticks.push([it, it]);
+                }
+                this.max = this.ticks[nt-1][1];
+            
+                this.tickFactor = tf;      
+                // determine number of minor ticks
+
+                this.numberMinorTicks = getnmt(this.tickPositions, this.tickInterval, this.tickFactor);     
+        
+                if (!this.numberMinorTicks) {
+                    this.numberMinorTicks = getnmt(this.tickPositions, this.tickInterval, this.tickFactor-1);
+                }
+            }
+            // max is free, min is fixed
+            else if (setmax) {
+                var tmax = min + (nt - 1) * ti;
+                if (max >= tmax) {
+                    max = tmax + ti;
+                    nt += 1;
+                }
+                else {
+                    max = tmax;
+                }
+
+                this.tickInterval = this.tickInterval || ti;
+                this.numberTicks = this.numberTicks || nt;
+                var it;
+                for (i=0; i<this.numberTicks; i++) {
+                    it = parseFloat((min+i*this.tickInterval).toFixed(11));
+                    this.ticks.push([it, it]);
+                }
+                this.max = this.ticks[this.numberTicks-1][1];
+            
+                this.tickFactor = tf;
+                // determine number of minor ticks
+                this.numberMinorTicks = getnmt(this.tickPositions, this.tickInterval, this.tickFactor);
+        
+                if (!this.numberMinorTicks) {
+                    this.numberMinorTicks = getnmt(this.tickPositions, this.tickInterval, this.tickFactor-1);
+                }
+            }
+            
+            // not setting max or min
+            if (!setmax && !setmin) {
+                var range = this.max - this.min;
+                tf = Math.floor(parseFloat((Math.log(range)/Math.log(10)).toFixed(11))) - 1;
+                var nticks = [5,6,4,7,3,8,9,10,2], res, numticks, nonSigDigits=0, sigRange;
+                // check to see how many zeros are at the end of the range
+                if (range > 1) {
+                    var rstr = String(range);
+                    if (rstr.search(/\./) == -1) {
+                         var pos = rstr.search(/0+$/);
+                         nonSigDigits = (pos > 0) ? rstr.length - pos - 1 : 0;
+                    }
+                }
+                sigRange = range/Math.pow(10, nonSigDigits);
+                for (i=0; i<nticks.length; i++) {
+                    res = sigRange/(nticks[i]-1);
+                    if (res == parseInt(res, 10)) {
+                        this.numberTicks = nticks[i];
+                        this.tickInterval = range/(this.numberTicks-1);
+                        this.tickFactor = tf+1;
+                        break;
+                    }
+                }
+                var it;
+                for (i=0; i<this.numberTicks; i++) {
+                    it = parseFloat((this.min+i*this.tickInterval).toFixed(11));
+                    this.ticks.push([it, it]);
+                }
+                // determine number of minor ticks
+                this.numberMinorTicks = getnmt(this.tickPositions, this.tickInterval, this.tickFactor);
+        
+                if (!this.numberMinorTicks) {
+                    this.numberMinorTicks = getnmt(this.tickPositions, this.tickInterval, this.tickFactor-1);
+                }
+                
+                if (!this.numberMinorTicks) {
+                    this.numberMinorTicks = 1;
+                    var nums = [4, 5, 3, 6, 2];
+                    for (i=0; i<5; i++) {
+                        var temp = this.tickInterval/nums[i];
+                        if (temp == parseInt(temp, 10)) {
+                            this.numberMinorTicks = nums[i]-1;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        
+
+        var r = this._radius,
+            sa = this.startAngle,
+            ea = this.endAngle,       
+            pi = Math.PI,
+            hpi = Math.PI/2;
+            
+        if (this.semiCircular) {
+            var overAngle = Math.atan(this.innerPad/r),
+                outersa = this.outerStartAngle = sa - overAngle,
+                outerea = this.outerEndAngle = ea + overAngle,
+                hubsa = this.hubStartAngle = sa - Math.atan(this.innerPad/this.hubRadius*2),
+                hubea = this.hubEndAngle = ea + Math.atan(this.innerPad/this.hubRadius*2);
+
+            ctx.save();            
+            
+            ctx.translate(this._center[0], this._center[1]);
+            ctx.lineJoin = "round";
+            ctx.lineCap = "round";
+            
+            // draw the innerbackground
+            ctx.save();
+            ctx.beginPath();  
+            ctx.fillStyle = this.background;
+            ctx.arc(0, 0, r, outersa, outerea, false);
+            ctx.closePath();
+            ctx.fill();
+            ctx.restore();
+            
+            // draw the shadow
+            // the outer ring.
+            var shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')';
+            ctx.save();
+            for (var i=0; i<this.shadowDepth; i++) {
+                ctx.translate(this.shadowOffset*Math.cos(this.shadowAngle/180*Math.PI), this.shadowOffset*Math.sin(this.shadowAngle/180*Math.PI));
+                ctx.beginPath();  
+                ctx.strokeStyle = shadowColor;
+                ctx.lineWidth = this.shadowWidth;
+                ctx.arc(0 ,0, r, outersa, outerea, false);
+                ctx.closePath();
+                ctx.stroke();
+            }
+            ctx.restore();
+            
+            // the inner hub.
+            ctx.save();
+            var tempd = parseInt((this.shadowDepth+1)/2, 10);
+            for (var i=0; i<tempd; i++) {
+                ctx.translate(this.shadowOffset*Math.cos(this.shadowAngle/180*Math.PI), this.shadowOffset*Math.sin(this.shadowAngle/180*Math.PI));
+                ctx.beginPath();  
+                ctx.fillStyle = shadowColor;
+                ctx.arc(0 ,0, this.hubRadius, hubsa, hubea, false);
+                ctx.closePath();
+                ctx.fill();
+            }
+            ctx.restore();
+            
+            // draw the outer ring.
+            ctx.save();
+            ctx.beginPath();  
+            ctx.strokeStyle = this.ringColor;
+            ctx.lineWidth = this.ringWidth;
+            ctx.arc(0 ,0, r, outersa, outerea, false);
+            ctx.closePath();
+            ctx.stroke();
+            ctx.restore();
+            
+            // draw the hub
+            
+            ctx.save();
+            ctx.beginPath();  
+            ctx.fillStyle = this.ringColor;
+            ctx.arc(0 ,0, this.hubRadius,hubsa, hubea, false);
+            ctx.closePath();
+            ctx.fill();
+            ctx.restore();
+            
+            // draw the ticks
+            if (this.showTicks) {
+                ctx.save();
+                var orad = this.tickOuterRadius,
+                    tl = this.tickLength,
+                    mtl = tl/2,
+                    nmt = this.numberMinorTicks,
+                    ts = this.span * Math.PI / 180 / (this.ticks.length-1),
+                    mts = ts/(nmt + 1);
+                
+                for (i = 0; i<this.ticks.length; i++) {
+                    ctx.beginPath();
+                    ctx.lineWidth = 1.5 + this.diameter/360;
+                    ctx.strokeStyle = this.ringColor;
+                    var wps = ts*i+sa;
+                    ctx.moveTo(-orad * Math.cos(ts*i+sa), orad * Math.sin(ts*i+sa));
+                    ctx.lineTo(-(orad-tl) * Math.cos(ts*i+sa), (orad - tl) * Math.sin(ts*i+sa));
+                    this._tickPoints.push([(orad-tl) * Math.cos(ts*i+sa) + this._center[0] + this.canvas._offsets.left, (orad - tl) * Math.sin(ts*i+sa) + this._center[1] + this.canvas._offsets.top, ts*i+sa]);
+                    ctx.stroke();
+                    ctx.lineWidth = 1.0 + this.diameter/440;
+                    if (i<this.ticks.length-1) {
+                        for (var j=1; j<=nmt; j++) {
+                            ctx.beginPath();
+                            ctx.moveTo(-orad * Math.cos(ts*i+mts*j+sa), orad * Math.sin(ts*i+mts*j+sa));
+                            ctx.lineTo(-(orad-mtl) * Math.cos(ts*i+mts*j+sa), (orad-mtl) * Math.sin(ts*i+mts*j+sa));
+                            ctx.stroke();
+                        }   
+                    }
+                }
+                ctx.restore();
+            }
+            
+            // draw the tick labels
+            if (this.showTickLabels) {
+                var elem, l, t, ew, eh, dim, maxdim=0;
+                var tp = this.tickPadding * (1 - 1/(this.diameter/80+1));
+                for (i=0; i<this.ticks.length; i++) {
+                    elem = $('<div class="jqplot-meterGauge-tick" style="position:absolute;">'+this.ticks[i][1]+'</div>');
+                    this.canvas._elem.after(elem);
+                    ew = elem.outerWidth(true);
+                    eh = elem.outerHeight(true);
+                    l = this._tickPoints[i][0] - ew * (this._tickPoints[i][2]-Math.PI)/Math.PI - tp * Math.cos(this._tickPoints[i][2]);
+                    t = this._tickPoints[i][1] - eh/2 + eh/2 * Math.pow(Math.abs((Math.sin(this._tickPoints[i][2]))), 0.5) + tp/3 * Math.pow(Math.abs((Math.sin(this._tickPoints[i][2]))), 0.5) ;
+                    // t = this._tickPoints[i][1] - eh/2 - eh/2 * Math.sin(this._tickPoints[i][2]) - tp/2 * Math.sin(this._tickPoints[i][2]);
+                    elem.css({left:l, top:t, color: this.tickColor});
+                    dim  = ew*Math.cos(this._tickPoints[i][2]) + eh*Math.sin(Math.PI/2+this._tickPoints[i][2]/2);
+                    maxdim = (dim > maxdim) ? dim : maxdim;
+                }
+            }
+            
+            // draw the gauge label
+            if (this.label && this.labelPosition == 'inside') {
+                var l = this._center[0] + this.canvas._offsets.left;
+                var tp = this.tickPadding * (1 - 1/(this.diameter/80+1));
+                var t = 0.5*(this._center[1] + this.canvas._offsets.top - this.hubRadius) + 0.5*(this._center[1] + this.canvas._offsets.top - this.tickOuterRadius + this.tickLength + tp) + this.labelHeightAdjust;
+                // this._labelElem = $('<div class="jqplot-meterGauge-label" style="position:absolute;">'+this.label+'</div>');
+                // this.canvas._elem.after(this._labelElem);
+                l -= this._labelElem.outerWidth(true)/2;
+                t -= this._labelElem.outerHeight(true)/2;
+                this._labelElem.css({left:l, top:t});
+            }
+            
+            else if (this.label && this.labelPosition == 'bottom') {
+                var l = this._center[0] + this.canvas._offsets.left - this._labelElem.outerWidth(true)/2;
+                var t = this._center[1] + this.canvas._offsets.top + this.innerPad + this.ringWidth + this.padding + this.labelHeightAdjust;
+                this._labelElem.css({left:l, top:t});
+                
+            }
+            
+            // draw the intervals
+            
+            ctx.save();
+            var inner = this.intervalInnerRadius || this.hubRadius * 1.5;
+            if (this.intervalOuterRadius == null) {
+                if (this.showTickLabels) {
+                    var outer = (this.tickOuterRadius - this.tickLength - this.tickPadding - this.diameter/8);
+                }
+                else {
+                    var outer = (this.tickOuterRadius - this.tickLength - this.diameter/16);
+                }
+            }
+            else {
+                var outer = this.intervalOuterRadius;
+            }
+            var range = this.max - this.min;
+            var intrange = this.intervals[this.intervals.length-1] - this.min;
+            var start, end, span = this.span*Math.PI/180;
+            for (i=0; i<this.intervals.length; i++) {
+                start = (i == 0) ? sa : sa + (this.intervals[i-1][0] - this.min)*span/range;
+                if (start < 0) {
+                    start = 0;
+                }
+                end = sa + (this.intervals[i][0] - this.min)*span/range;
+                if (end < 0) {
+                    end = 0;
+                }
+                ctx.beginPath();
+                ctx.fillStyle = this.intervals[i][2];
+                ctx.arc(0, 0, inner, start, end, false);
+                ctx.lineTo(outer*Math.cos(end), outer*Math.sin(end));
+                ctx.arc(0, 0, outer, end, start, true);
+                ctx.lineTo(inner*Math.cos(start), inner*Math.sin(start));
+                ctx.closePath();
+                ctx.fill();
+            }
+            ctx.restore();
+            
+            // draw the needle
+            var datapoint = this.data[0][1];
+            var dataspan = this.max - this.min;
+            if (this.pegNeedle) {
+                if (this.data[0][1] > this.max + dataspan*3/this.span) {
+                    datapoint = this.max + dataspan*3/this.span;
+                }
+                if (this.data[0][1] < this.min - dataspan*3/this.span) {
+                    datapoint = this.min - dataspan*3/this.span;
+                }
+            }
+            var dataang = (datapoint - this.min)/dataspan * this.span * Math.PI/180 + this.startAngle;
+            
+            
+            ctx.save();
+            ctx.beginPath();
+            ctx.fillStyle = this.ringColor;
+            ctx.strokeStyle = this.ringColor;
+            this.needleLength = (this.tickOuterRadius - this.tickLength) * 0.85;
+            this.needleThickness = (this.needleThickness < 2) ? 2 : this.needleThickness;
+            var endwidth = this.needleThickness * 0.4;
+
+            
+            var dl = this.needleLength/10;
+            var dt = (this.needleThickness - endwidth)/10;
+            var templ;
+            for (var i=0; i<10; i++) {
+                templ = this.needleThickness - i*dt;
+                ctx.moveTo(dl*i*Math.cos(dataang), dl*i*Math.sin(dataang));
+                ctx.lineWidth = templ;
+                ctx.lineTo(dl*(i+1)*Math.cos(dataang), dl*(i+1)*Math.sin(dataang));
+                ctx.stroke();
+            }
+            
+            ctx.restore();
+        }
+        else {
+            this._center = [(cw - trans * offx)/2 + trans * offx, (ch - trans*offy)/2 + trans * offy];
+        }               
+    };
+    
+    $.jqplot.MeterGaugeAxisRenderer = function() {
+        $.jqplot.LinearAxisRenderer.call(this);
+    };
+    
+    $.jqplot.MeterGaugeAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+    $.jqplot.MeterGaugeAxisRenderer.prototype.constructor = $.jqplot.MeterGaugeAxisRenderer;
+        
+    
+    // There are no traditional axes on a gauge chart.  We just need to provide
+    // dummy objects with properties so the plot will render.
+    // called with scope of axis object.
+    $.jqplot.MeterGaugeAxisRenderer.prototype.init = function(options){
+        //
+        this.tickRenderer = $.jqplot.MeterGaugeTickRenderer;
+        $.extend(true, this, options);
+        // I don't think I'm going to need _dataBounds here.
+        // have to go Axis scaling in a way to fit chart onto plot area
+        // and provide u2p and p2u functionality for mouse cursor, etc.
+        // for convienence set _dataBounds to 0 and 100 and
+        // set min/max to 0 and 100.
+        this._dataBounds = {min:0, max:100};
+        this.min = 0;
+        this.max = 100;
+        this.showTicks = false;
+        this.ticks = [];
+        this.showMark = false;
+        this.show = false; 
+    };
+    
+    $.jqplot.MeterGaugeLegendRenderer = function(){
+        $.jqplot.TableLegendRenderer.call(this);
+    };
+    
+    $.jqplot.MeterGaugeLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
+    $.jqplot.MeterGaugeLegendRenderer.prototype.constructor = $.jqplot.MeterGaugeLegendRenderer;
+    
+    /**
+     * Class: $.jqplot.MeterGaugeLegendRenderer
+     *Meter gauges don't typically have a legend, this overrides the default legend renderer.
+     */
+    $.jqplot.MeterGaugeLegendRenderer.prototype.init = function(options) {
+        // Maximum number of rows in the legend.  0 or null for unlimited.
+        this.numberRows = null;
+        // Maximum number of columns in the legend.  0 or null for unlimited.
+        this.numberColumns = null;
+        $.extend(true, this, options);
+    };
+    
+    // called with context of legend
+    $.jqplot.MeterGaugeLegendRenderer.prototype.draw = function() {
+        if (this.show) {
+            var series = this._series;
+            var ss = 'position:absolute;';
+            ss += (this.background) ? 'background:'+this.background+';' : '';
+            ss += (this.border) ? 'border:'+this.border+';' : '';
+            ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
+            ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
+            ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
+            ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
+            ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
+            ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
+            ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
+            this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>');
+            // MeterGauge charts legends don't go by number of series, but by number of data points
+            // in the series.  Refactor things here for that.
+            
+            var pad = false, 
+                reverse = false,
+                nr, nc;
+            var s = series[0];
+            
+            if (s.show) {
+                var pd = s.data;
+                if (this.numberRows) {
+                    nr = this.numberRows;
+                    if (!this.numberColumns){
+                        nc = Math.ceil(pd.length/nr);
+                    }
+                    else{
+                        nc = this.numberColumns;
+                    }
+                }
+                else if (this.numberColumns) {
+                    nc = this.numberColumns;
+                    nr = Math.ceil(pd.length/this.numberColumns);
+                }
+                else {
+                    nr = pd.length;
+                    nc = 1;
+                }
+                
+                var i, j, tr, td1, td2, lt, rs, color;
+                var idx = 0;    
+                
+                for (i=0; i<nr; i++) {
+                    if (reverse){
+                        tr = $('<tr class="jqplot-table-legend"></tr>').prependTo(this._elem);
+                    }
+                    else{
+                        tr = $('<tr class="jqplot-table-legend"></tr>').appendTo(this._elem);
+                    }
+                    for (j=0; j<nc; j++) {
+                        if (idx < pd.length){
+                            // debugger
+                            lt = this.labels[idx] || pd[idx][0].toString();
+                            color = s.color;
+                            if (!reverse){
+                                if (i>0){
+                                    pad = true;
+                                }
+                                else{
+                                    pad = false;
+                                }
+                            }
+                            else{
+                                if (i == nr -1){
+                                    pad = false;
+                                }
+                                else{
+                                    pad = true;
+                                }
+                            }
+                            rs = (pad) ? this.rowSpacing : '0';
+                
+                            td1 = $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
+                                '<div><div class="jqplot-table-legend-swatch" style="border-color:'+color+';"></div>'+
+                                '</div></td>');
+                            td2 = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
+                            if (this.escapeHtml){
+                                td2.text(lt);
+                            }
+                            else {
+                                td2.html(lt);
+                            }
+                            if (reverse) {
+                                td2.prependTo(tr);
+                                td1.prependTo(tr);
+                            }
+                            else {
+                                td1.appendTo(tr);
+                                td2.appendTo(tr);
+                            }
+                            pad = true;
+                        }
+                        idx++;
+                    }   
+                }
+            }
+        }
+        return this._elem;                
+    };
+    
+    
+    // setup default renderers for axes and legend so user doesn't have to
+    // called with scope of plot
+    function preInit(target, data, options) {
+        // debugger
+        options = options || {};
+        options.axesDefaults = options.axesDefaults || {};
+        options.legend = options.legend || {};
+        options.seriesDefaults = options.seriesDefaults || {};
+        options.grid = options.grid || {};
+           
+        // only set these if there is a gauge series
+        var setopts = false;
+        if (options.seriesDefaults.renderer == $.jqplot.MeterGaugeRenderer) {
+            setopts = true;
+        }
+        else if (options.series) {
+            for (var i=0; i < options.series.length; i++) {
+                if (options.series[i].renderer == $.jqplot.MeterGaugeRenderer) {
+                    setopts = true;
+                }
+            }
+        }
+        
+        if (setopts) {
+            options.axesDefaults.renderer = $.jqplot.MeterGaugeAxisRenderer;
+            options.legend.renderer = $.jqplot.MeterGaugeLegendRenderer;
+            options.legend.preDraw = true;
+            options.grid.background = options.grid.background || 'white';
+            options.grid.drawGridlines = false;
+            options.grid.borderWidth = (options.grid.borderWidth != null) ? options.grid.borderWidth : 0;
+            options.grid.shadow = (options.grid.shadow != null) ? options.grid.shadow : false;
+        }
+    }
+    
+    // called with scope of plot
+    function postParseOptions(options) {
+        //
+    }
+    
+    $.jqplot.preInitHooks.push(preInit);
+    $.jqplot.postParseOptionsHooks.push(postParseOptions);
+    
+    $.jqplot.MeterGaugeTickRenderer = function() {
+        $.jqplot.AxisTickRenderer.call(this);
+    };
+    
+    $.jqplot.MeterGaugeTickRenderer.prototype = new $.jqplot.AxisTickRenderer();
+    $.jqplot.MeterGaugeTickRenderer.prototype.constructor = $.jqplot.MeterGaugeTickRenderer;
+    
+})(jQuery);
+    
+    
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.mobile.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.mobile.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.mobile.js	(revision 2119)
@@ -0,0 +1,45 @@
+/**
+ * jqplot.jquerymobile plugin
+ * jQuery Mobile virtual event support.
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2011 Takashi Okamoto
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ */
+(function($) {
+    function postInit(target, data, options){
+        this.bindCustomEvents = function() {
+            this.eventCanvas._elem.bind('vclick', {plot:this}, this.onClick);
+            this.eventCanvas._elem.bind('dblclick', {plot:this}, this.onDblClick);
+            this.eventCanvas._elem.bind('taphold', {plot:this}, this.onDblClick);
+            this.eventCanvas._elem.bind('vmousedown', {plot:this}, this.onMouseDown);
+            this.eventCanvas._elem.bind('vmousemove', {plot:this}, this.onMouseMove);
+            this.eventCanvas._elem.bind('mouseenter', {plot:this}, this.onMouseEnter);
+            this.eventCanvas._elem.bind('mouseleave', {plot:this}, this.onMouseLeave);
+            if (this.captureRightClick) {
+                this.eventCanvas._elem.bind('vmouseup', {plot:this}, this.onRightClick);
+                this.eventCanvas._elem.get(0).oncontextmenu = function() {
+                    return false;
+                };
+            }
+            else {
+                this.eventCanvas._elem.bind('vmouseup', {plot:this}, this.onMouseUp);
+            }
+        };
+        this.plugins.mobile = true;
+    }
+    $.jqplot.postInitHooks.push(postInit);
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.ohlcRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.ohlcRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.ohlcRenderer.js	(revision 2119)
@@ -0,0 +1,373 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    /**
+     * Class: $.jqplot.OHLCRenderer
+     * jqPlot Plugin to draw Open Hi Low Close, Candlestick and Hi Low Close charts.
+     * 
+     * To use this plugin, include the renderer js file in 
+     * your source:
+     * 
+     * > <script type="text/javascript" src="plugins/jqplot.ohlcRenderer.js"></script>
+     * 
+     * You will most likely want to use a date axis renderer
+     * for the x axis also, so include the date axis render js file also:
+     * 
+     * > <script type="text/javascript" src="plugins/jqplot.dateAxisRenderer.js"></script>
+     * 
+     * Then you set the renderer in the series options on your plot:
+     * 
+     * > series: [{renderer:$.jqplot.OHLCRenderer}]
+     * 
+     * For OHLC and candlestick charts, data should be specified
+     * like so:
+     * 
+     * > dat = [['07/06/2009',138.7,139.68,135.18,135.4], ['06/29/2009',143.46,144.66,139.79,140.02], ...]
+     * 
+     * If the data array has only 4 values per point instead of 5,
+     * the renderer will create a Hi Low Close chart instead.  In that case,
+     * data should be supplied like:
+     * 
+     * > dat = [['07/06/2009',139.68,135.18,135.4], ['06/29/2009',144.66,139.79,140.02], ...]
+     * 
+     * To generate a candlestick chart instead of an OHLC chart,
+     * set the "candlestick" option to true:
+     * 
+     * > series: [{renderer:$.jqplot.OHLCRenderer, rendererOptions:{candleStick:true}}],
+     * 
+     */
+    $.jqplot.OHLCRenderer = function(){
+        // subclass line renderer to make use of some of its methods.
+        $.jqplot.LineRenderer.call(this);
+        // prop: candleStick
+        // true to render chart as candleStick.
+        // Must have an open price, cannot be a hlc chart.
+        this.candleStick = false;
+        // prop: tickLength
+        // length of the line in pixels indicating open and close price.
+        // Default will auto calculate based on plot width and 
+        // number of points displayed.
+        this.tickLength = 'auto';
+        // prop: bodyWidth
+        // width of the candlestick body in pixels.  Default will auto calculate
+        // based on plot width and number of candlesticks displayed.
+        this.bodyWidth = 'auto';
+        // prop: openColor
+        // color of the open price tick mark.  Default is series color.
+        this.openColor = null;
+        // prop: closeColor
+        // color of the close price tick mark.  Default is series color.
+        this.closeColor = null;
+        // prop: wickColor
+        // color of the hi-lo line thorugh the candlestick body.
+        // Default is the series color.
+        this.wickColor = null;
+        // prop: fillUpBody
+        // true to render an "up" day (close price greater than open price)
+        // with a filled candlestick body.
+        this.fillUpBody = false;
+        // prop: fillDownBody
+        // true to render a "down" day (close price lower than open price)
+        // with a filled candlestick body.
+        this.fillDownBody = true;
+        // prop: upBodyColor
+        // Color of candlestick body of an "up" day.  Default is series color.
+        this.upBodyColor = null;
+        // prop: downBodyColor
+        // Color of candlestick body on a "down" day.  Default is series color.
+        this.downBodyColor = null;
+        // prop: hlc
+        // true if is a hi-low-close chart (no open price).
+        // This is determined automatically from the series data.
+        this.hlc = false;
+        // prop: lineWidth
+        // Width of the hi-low line and open/close ticks.
+        // Must be set in the rendererOptions for the series.
+        this.lineWidth = 1.5;
+        this._tickLength;
+        this._bodyWidth;
+    };
+    
+    $.jqplot.OHLCRenderer.prototype = new $.jqplot.LineRenderer();
+    $.jqplot.OHLCRenderer.prototype.constructor = $.jqplot.OHLCRenderer;
+    
+    // called with scope of series.
+    $.jqplot.OHLCRenderer.prototype.init = function(options) {
+        options = options || {};
+        // lineWidth has to be set on the series, changes in renderer
+        // constructor have no effect.  set the default here
+        // if no renderer option for lineWidth is specified.
+        this.lineWidth = options.lineWidth || 1.5;
+        $.jqplot.LineRenderer.prototype.init.call(this, options);
+        this._type = 'ohlc';
+        // set the yaxis data bounds here to account for hi and low values
+        var db = this._yaxis._dataBounds;
+        var d = this._plotData;
+        // if data points have less than 5 values, force a hlc chart.
+        if (d[0].length < 5) {
+            this.renderer.hlc = true;
+
+            for (var j=0; j<d.length; j++) { 
+                if (d[j][2] < db.min || db.min == null) {
+                    db.min = d[j][2];
+                }
+                if (d[j][1] > db.max || db.max == null) {
+                    db.max = d[j][1];
+                }             
+            }
+        }
+        else {
+            for (var j=0; j<d.length; j++) { 
+                if (d[j][3] < db.min || db.min == null) {
+                    db.min = d[j][3];
+                }
+                if (d[j][2] > db.max || db.max == null) {
+                    db.max = d[j][2];
+                }             
+            }
+        }
+        
+    };
+    
+    // called within scope of series.
+    $.jqplot.OHLCRenderer.prototype.draw = function(ctx, gd, options) {
+        var d = this.data;
+        var xmin = this._xaxis.min;
+        var xmax = this._xaxis.max;
+        // index of last value below range of plot.
+        var xminidx = 0;
+        // index of first value above range of plot.
+        var xmaxidx = d.length;
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        var i, prevColor, ops, b, h, w, a, points;
+        var o;
+        var r = this.renderer;
+        var opts = (options != undefined) ? options : {};
+        var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+        var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+        var fillAndStroke = (opts.fillAndStroke != undefined) ? opts.fillAndStroke : this.fillAndStroke;
+        r.bodyWidth = (opts.bodyWidth != undefined) ? opts.bodyWidth : r.bodyWidth;
+        r.tickLength = (opts.tickLength != undefined) ? opts.tickLength : r.tickLength;
+        ctx.save();
+        if (this.show) {
+            var x, open, hi, low, close;
+            // need to get widths based on number of points shown,
+            // not on total number of points.  Use the results 
+            // to speed up drawing in next step.
+            for (var i=0; i<d.length; i++) {
+                if (d[i][0] < xmin) {
+                    xminidx = i;
+                }
+                else if (d[i][0] < xmax) {
+                    xmaxidx = i+1;
+                }
+            }
+
+            var dwidth = this.gridData[xmaxidx-1][0] - this.gridData[xminidx][0];
+            var nvisiblePoints = xmaxidx - xminidx;
+            try {
+                var dinterval = Math.abs(this._xaxis.series_u2p(parseInt(this._xaxis._intervalStats[0].sortedIntervals[0].interval, 10)) - this._xaxis.series_u2p(0)); 
+            }
+
+            catch (e) {
+                var dinterval = dwidth / nvisiblePoints;
+            }
+            
+            if (r.candleStick) {
+                if (typeof(r.bodyWidth) == 'number') {
+                    r._bodyWidth = r.bodyWidth;
+                }
+                else {
+                    r._bodyWidth = Math.min(20, dinterval/1.65);
+                }
+            }
+            else {
+                if (typeof(r.tickLength) == 'number') {
+                    r._tickLength = r.tickLength;
+                }
+                else {
+                    r._tickLength = Math.min(10, dinterval/3.5);
+                }
+            }
+            
+            for (var i=xminidx; i<xmaxidx; i++) {
+                x = xp(d[i][0]);
+                if (r.hlc) {
+                    open = null;
+                    hi = yp(d[i][1]);
+                    low = yp(d[i][2]);
+                    close = yp(d[i][3]);
+                }
+                else {
+                    open = yp(d[i][1]);
+                    hi = yp(d[i][2]);
+                    low = yp(d[i][3]);
+                    close = yp(d[i][4]);
+                }
+                o = {};
+                if (r.candleStick && !r.hlc) {
+                    w = r._bodyWidth;
+                    a = x - w/2;
+                    // draw candle
+                    // determine if candle up or down
+                    // up, remember grid coordinates increase downward
+                    if (close < open) {
+                        // draw wick
+                        if (r.wickColor) {
+                            o.color = r.wickColor;
+                        }
+                        else if (r.downBodyColor) {
+                            o.color = r.upBodyColor;
+                        }
+                        ops = $.extend(true, {}, opts, o);
+                        r.shapeRenderer.draw(ctx, [[x, hi], [x, close]], ops); 
+                        r.shapeRenderer.draw(ctx, [[x, open], [x, low]], ops); 
+                        o = {};
+                        b = close;
+                        h = open - close;
+                        // if color specified, use it
+                        if (r.fillUpBody) {
+                            o.fillRect = true;
+                        }
+                        else {
+                            o.strokeRect = true;
+                            w = w - this.lineWidth;
+                            a = x - w/2;
+                        }
+                        if (r.upBodyColor) {
+                            o.color = r.upBodyColor;
+                            o.fillStyle = r.upBodyColor;
+                        }
+                        points = [a, b, w, h];
+                    }
+                    // down
+                    else if (close >  open) {
+                        // draw wick
+                        if (r.wickColor) {
+                            o.color = r.wickColor;
+                        }
+                        else if (r.downBodyColor) {
+                            o.color = r.downBodyColor;
+                        }
+                        ops = $.extend(true, {}, opts, o);
+                        r.shapeRenderer.draw(ctx, [[x, hi], [x, open]], ops); 
+                        r.shapeRenderer.draw(ctx, [[x, close], [x, low]], ops);
+                         
+                        o = {};
+                        
+                        b = open;
+                        h = close - open;
+                        // if color specified, use it
+                        if (r.fillDownBody) {
+                            o.fillRect = true;
+                        }
+                        else {
+                            o.strokeRect = true;
+                            w = w - this.lineWidth;
+                            a = x - w/2;
+                        }
+                        if (r.downBodyColor) {
+                            o.color = r.downBodyColor;
+                            o.fillStyle = r.downBodyColor;
+                        }
+                        points = [a, b, w, h];
+                    }
+                    // even, open = close
+                    else  {
+                        // draw wick
+                        if (r.wickColor) {
+                            o.color = r.wickColor;
+                        }
+                        ops = $.extend(true, {}, opts, o);
+                        r.shapeRenderer.draw(ctx, [[x, hi], [x, low]], ops); 
+                        o = {};
+                        o.fillRect = false;
+                        o.strokeRect = false;
+                        a = [x - w/2, open];
+                        b = [x + w/2, close];
+                        w = null;
+                        h = null;
+                        points = [a, b];
+                    }
+                    ops = $.extend(true, {}, opts, o);
+                    r.shapeRenderer.draw(ctx, points, ops);
+                }
+                else {
+                    prevColor = opts.color;
+                    if (r.openColor) {
+                        opts.color = r.openColor;
+                    }
+                    // draw open tick
+                    if (!r.hlc) {
+                        r.shapeRenderer.draw(ctx, [[x-r._tickLength, open], [x, open]], opts);    
+                    }
+                    opts.color = prevColor;
+                    // draw wick
+                    if (r.wickColor) {
+                        opts.color = r.wickColor;
+                    }
+                    r.shapeRenderer.draw(ctx, [[x, hi], [x, low]], opts); 
+                    opts.color  = prevColor;
+                    // draw close tick
+                    if (r.closeColor) {
+                        opts.color = r.closeColor;
+                    }
+                    r.shapeRenderer.draw(ctx, [[x, close], [x+r._tickLength, close]], opts); 
+                    opts.color = prevColor;
+                }
+            }
+        }
+        
+        ctx.restore();
+    };  
+    
+    $.jqplot.OHLCRenderer.prototype.drawShadow = function(ctx, gd, options) {
+        // This is a no-op, shadows drawn with lines.
+    };
+    
+    // called with scope of plot.
+    $.jqplot.OHLCRenderer.checkOptions = function(target, data, options) {
+        // provide some sensible highlighter options by default
+        // These aren't good for hlc, only for ohlc or candlestick
+        if (!options.highlighter) {
+            options.highlighter = {
+                showMarker:false,
+                tooltipAxes: 'y',
+                yvalues: 4,
+                formatString:'<table class="jqplot-highlighter"><tr><td>date:</td><td>%s</td></tr><tr><td>open:</td><td>%s</td></tr><tr><td>hi:</td><td>%s</td></tr><tr><td>low:</td><td>%s</td></tr><tr><td>close:</td><td>%s</td></tr></table>'
+            };
+        }
+    };
+    
+    //$.jqplot.preInitHooks.push($.jqplot.OHLCRenderer.checkOptions);
+    
+})(jQuery);    
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pieRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pieRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pieRenderer.js	(revision 2119)
@@ -0,0 +1,904 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    /**
+     * Class: $.jqplot.PieRenderer
+     * Plugin renderer to draw a pie chart.
+     * x values, if present, will be used as slice labels.
+     * y values give slice size.
+     * 
+     * To use this renderer, you need to include the 
+     * pie renderer plugin, for example:
+     * 
+     * > <script type="text/javascript" src="plugins/jqplot.pieRenderer.js"></script>
+     * 
+     * Properties described here are passed into the $.jqplot function
+     * as options on the series renderer.  For example:
+     * 
+     * > plot2 = $.jqplot('chart2', [s1, s2], {
+     * >     seriesDefaults: {
+     * >         renderer:$.jqplot.PieRenderer,
+     * >         rendererOptions:{
+     * >              sliceMargin: 2,
+     * >              startAngle: -90
+     * >          }
+     * >      }
+     * > });
+     * 
+     * A pie plot will trigger events on the plot target
+     * according to user interaction.  All events return the event object,
+     * the series index, the point (slice) index, and the point data for 
+     * the appropriate slice.
+     * 
+     * 'jqplotDataMouseOver' - triggered when user mouseing over a slice.
+     * 'jqplotDataHighlight' - triggered the first time user mouses over a slice,
+     * if highlighting is enabled.
+     * 'jqplotDataUnhighlight' - triggered when a user moves the mouse out of
+     * a highlighted slice.
+     * 'jqplotDataClick' - triggered when the user clicks on a slice.
+     * 'jqplotDataRightClick' - tiggered when the user right clicks on a slice if
+     * the "captureRightClick" option is set to true on the plot.
+     */
+    $.jqplot.PieRenderer = function(){
+        $.jqplot.LineRenderer.call(this);
+    };
+    
+    $.jqplot.PieRenderer.prototype = new $.jqplot.LineRenderer();
+    $.jqplot.PieRenderer.prototype.constructor = $.jqplot.PieRenderer;
+    
+    // called with scope of a series
+    $.jqplot.PieRenderer.prototype.init = function(options, plot) {
+        // Group: Properties
+        //
+        // prop: diameter
+        // Outer diameter of the pie, auto computed by default
+        this.diameter = null;
+        // prop: padding
+        // padding between the pie and plot edges, legend, etc.
+        this.padding = 20;
+        // prop: sliceMargin
+        // angular spacing between pie slices in degrees.
+        this.sliceMargin = 0;
+        // prop: fill
+        // true or false, whether to fil the slices.
+        this.fill = true;
+        // prop: shadowOffset
+        // offset of the shadow from the slice and offset of 
+        // each succesive stroke of the shadow from the last.
+        this.shadowOffset = 2;
+        // prop: shadowAlpha
+        // transparency of the shadow (0 = transparent, 1 = opaque)
+        this.shadowAlpha = 0.07;
+        // prop: shadowDepth
+        // number of strokes to apply to the shadow, 
+        // each stroke offset shadowOffset from the last.
+        this.shadowDepth = 5;
+        // prop: highlightMouseOver
+        // True to highlight slice when moused over.
+        // This must be false to enable highlightMouseDown to highlight when clicking on a slice.
+        this.highlightMouseOver = true;
+        // prop: highlightMouseDown
+        // True to highlight when a mouse button is pressed over a slice.
+        // This will be disabled if highlightMouseOver is true.
+        this.highlightMouseDown = false;
+        // prop: highlightColors
+        // an array of colors to use when highlighting a slice.
+        this.highlightColors = [];
+        // prop: dataLabels
+        // Either 'label', 'value', 'percent' or an array of labels to place on the pie slices.
+        // Defaults to percentage of each pie slice.
+        this.dataLabels = 'percent';
+        // prop: showDataLabels
+        // true to show data labels on slices.
+        this.showDataLabels = false;
+        // prop: dataLabelFormatString
+        // Format string for data labels.  If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage.
+        this.dataLabelFormatString = null;
+        // prop: dataLabelThreshold
+        // Threshhold in percentage (0-100) of pie area, below which no label will be displayed.
+        // This applies to all label types, not just to percentage labels.
+        this.dataLabelThreshold = 3;
+        // prop: dataLabelPositionFactor
+        // A Multiplier (0-1) of the pie radius which controls position of label on slice.
+        // Increasing will slide label toward edge of pie, decreasing will slide label toward center of pie.
+        this.dataLabelPositionFactor = 0.52;
+        // prop: dataLabelNudge
+        // Number of pixels to slide the label away from (+) or toward (-) the center of the pie.
+        this.dataLabelNudge = 2;
+        // prop: dataLabelCenterOn
+        // True to center the data label at its position.
+        // False to set the inside facing edge of the label at its position.
+        this.dataLabelCenterOn = true;
+        // prop: startAngle
+        // Angle to start drawing pie in degrees.  
+        // According to orientation of canvas coordinate system:
+        // 0 = on the positive x axis
+        // -90 = on the positive y axis.
+        // 90 = on the negaive y axis.
+        // 180 or - 180 = on the negative x axis.
+        this.startAngle = 0;
+        this.tickRenderer = $.jqplot.PieTickRenderer;
+        // Used as check for conditions where pie shouldn't be drawn.
+        this._drawData = true;
+        this._type = 'pie';
+        
+        // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
+        if (options.highlightMouseDown && options.highlightMouseOver == null) {
+            options.highlightMouseOver = false;
+        }
+        
+        $.extend(true, this, options);
+
+        if (this.sliceMargin < 0) {
+            this.sliceMargin = 0;
+        }
+
+        this._diameter = null;
+        this._radius = null;
+        // array of [start,end] angles arrays, one for each slice.  In radians.
+        this._sliceAngles = [];
+        // index of the currenty highlighted point, if any
+        this._highlightedPoint = null;
+        
+        // set highlight colors if none provided
+        if (this.highlightColors.length == 0) {
+            for (var i=0; i<this.seriesColors.length; i++){
+                var rgba = $.jqplot.getColorComponents(this.seriesColors[i]);
+                var newrgb = [rgba[0], rgba[1], rgba[2]];
+                var sum = newrgb[0] + newrgb[1] + newrgb[2];
+                for (var j=0; j<3; j++) {
+                    // when darkening, lowest color component can be is 60.
+                    newrgb[j] = (sum > 570) ?  newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
+                    newrgb[j] = parseInt(newrgb[j], 10);
+                }
+                this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
+            }
+        }
+        
+        this.highlightColorGenerator = new $.jqplot.ColorGenerator(this.highlightColors);
+        
+        plot.postParseOptionsHooks.addOnce(postParseOptions);
+        plot.postInitHooks.addOnce(postInit);
+        plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
+        plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
+        plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
+        plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
+        plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
+        plot.postDrawHooks.addOnce(postPlotDraw);
+    };
+    
+    $.jqplot.PieRenderer.prototype.setGridData = function(plot) {
+        // set gridData property.  This will hold angle in radians of each data point.
+        var stack = [];
+        var td = [];
+        var sa = this.startAngle/180*Math.PI;
+        var tot = 0;
+        // don't know if we have any valid data yet, so set plot to not draw.
+        this._drawData = false;
+        for (var i=0; i<this.data.length; i++){
+            if (this.data[i][1] != 0) {
+                // we have data, O.K. to draw.
+                this._drawData = true;
+            }
+            stack.push(this.data[i][1]);
+            td.push([this.data[i][0]]);
+            if (i>0) {
+                stack[i] += stack[i-1];
+            }
+            tot += this.data[i][1];
+        }
+        var fact = Math.PI*2/stack[stack.length - 1];
+        
+        for (var i=0; i<stack.length; i++) {
+            td[i][1] = stack[i] * fact;
+            td[i][2] = this.data[i][1]/tot;
+        }
+        this.gridData = td;
+    };
+    
+    $.jqplot.PieRenderer.prototype.makeGridData = function(data, plot) {
+        var stack = [];
+        var td = [];
+        var tot = 0;
+        var sa = this.startAngle/180*Math.PI;
+        // don't know if we have any valid data yet, so set plot to not draw.
+        this._drawData = false;
+        for (var i=0; i<data.length; i++){
+            if (this.data[i][1] != 0) {
+                // we have data, O.K. to draw.
+                this._drawData = true;
+            }
+            stack.push(data[i][1]);
+            td.push([data[i][0]]);
+            if (i>0) {
+                stack[i] += stack[i-1];
+            }
+            tot += data[i][1];
+        }
+        var fact = Math.PI*2/stack[stack.length - 1];
+        
+        for (var i=0; i<stack.length; i++) {
+            td[i][1] = stack[i] * fact;
+            td[i][2] = data[i][1]/tot;
+        }
+        return td;
+    };
+
+    function calcRadiusAdjustment(ang) {
+        return Math.sin((ang - (ang-Math.PI) / 8 / Math.PI )/2.0);
+    }
+
+    function calcRPrime(ang1, ang2, sliceMargin, fill, lineWidth) {
+        var rprime = 0;
+        var ang = ang2 - ang1;
+        var absang = Math.abs(ang);
+        var sm = sliceMargin;
+        if (fill == false) {
+            sm += lineWidth;
+        }
+
+        if (sm > 0 && absang > 0.01 && absang < 6.282) {
+            rprime = parseFloat(sm) / 2.0 / calcRadiusAdjustment(ang);
+        }
+
+        return rprime;
+    }
+    
+    $.jqplot.PieRenderer.prototype.drawSlice = function (ctx, ang1, ang2, color, isShadow) {
+        if (this._drawData) {
+            var r = this._radius;
+            var fill = this.fill;
+            var lineWidth = this.lineWidth;
+            var sm = this.sliceMargin;
+            if (this.fill == false) {
+                sm += this.lineWidth;
+            }
+            ctx.save();
+            ctx.translate(this._center[0], this._center[1]);
+            
+            var rprime = calcRPrime(ang1, ang2, this.sliceMargin, this.fill, this.lineWidth);
+
+            var transx = rprime * Math.cos((ang1 + ang2) / 2.0);
+            var transy = rprime * Math.sin((ang1 + ang2) / 2.0);
+
+            if ((ang2 - ang1) <= Math.PI) {
+                r -= rprime;  
+            }
+            else {
+                r += rprime;
+            }
+
+            ctx.translate(transx, transy);
+            
+            if (isShadow) {
+                for (var i=0, l=this.shadowDepth; i<l; i++) {
+                    ctx.save();
+                    ctx.translate(this.shadowOffset*Math.cos(this.shadowAngle/180*Math.PI), this.shadowOffset*Math.sin(this.shadowAngle/180*Math.PI));
+                    doDraw(r);
+                }
+                for (var i=0, l=this.shadowDepth; i<l; i++) {
+                    ctx.restore();
+                }
+            }
+    
+            else {
+                doDraw(r);
+            }
+            ctx.restore();
+        }
+    
+        function doDraw (rad) {
+            // Fix for IE and Chrome that can't seem to draw circles correctly.
+            // ang2 should always be <= 2 pi since that is the way the data is converted.
+            // 2Pi = 6.2831853, Pi = 3.1415927
+             if (ang2 > 6.282 + this.startAngle) {
+                ang2 = 6.282 + this.startAngle;
+                if (ang1 > ang2) {
+                    ang1 = 6.281 + this.startAngle;
+                }
+            }
+            // Fix for IE, where it can't seem to handle 0 degree angles.  Also avoids
+            // ugly line on unfilled pies.
+            if (ang1 >= ang2) {
+                return;
+            }            
+        
+            ctx.beginPath();  
+            ctx.fillStyle = color;
+            ctx.strokeStyle = color;
+            ctx.lineWidth = lineWidth;
+            ctx.arc(0, 0, rad, ang1, ang2, false);
+            ctx.lineTo(0,0);
+            ctx.closePath();
+        
+            if (fill) {
+                ctx.fill();
+            }
+            else {
+                ctx.stroke();
+            }
+        }
+    };
+    
+    // called with scope of series
+    $.jqplot.PieRenderer.prototype.draw = function (ctx, gd, options, plot) {
+        var i;
+        var opts = (options != undefined) ? options : {};
+        // offset and direction of offset due to legend placement
+        var offx = 0;
+        var offy = 0;
+        var trans = 1;
+        var colorGenerator = new $.jqplot.ColorGenerator(this.seriesColors);
+        if (options.legendInfo && options.legendInfo.placement == 'insideGrid') {
+            var li = options.legendInfo;
+            switch (li.location) {
+                case 'nw':
+                    offx = li.width + li.xoffset;
+                    break;
+                case 'w':
+                    offx = li.width + li.xoffset;
+                    break;
+                case 'sw':
+                    offx = li.width + li.xoffset;
+                    break;
+                case 'ne':
+                    offx = li.width + li.xoffset;
+                    trans = -1;
+                    break;
+                case 'e':
+                    offx = li.width + li.xoffset;
+                    trans = -1;
+                    break;
+                case 'se':
+                    offx = li.width + li.xoffset;
+                    trans = -1;
+                    break;
+                case 'n':
+                    offy = li.height + li.yoffset;
+                    break;
+                case 's':
+                    offy = li.height + li.yoffset;
+                    trans = -1;
+                    break;
+                default:
+                    break;
+            }
+        }
+        
+        var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+        var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+        var cw = ctx.canvas.width;
+        var ch = ctx.canvas.height;
+        var w = cw - offx - 2 * this.padding;
+        var h = ch - offy - 2 * this.padding;
+        var mindim = Math.min(w,h);
+        var d = mindim;
+        
+        // Fixes issue #272.  Thanks hugwijst!
+        // reset slice angles array.
+        this._sliceAngles = [];
+
+        var sm = this.sliceMargin;
+        if (this.fill == false) {
+            sm += this.lineWidth;
+        }
+        
+        var rprime;
+        var maxrprime = 0;
+
+        var ang, ang1, ang2, shadowColor;
+        var sa = this.startAngle / 180 * Math.PI;
+
+        // have to pre-draw shadows, so loop throgh here and calculate some values also.
+        for (var i=0, l=gd.length; i<l; i++) {
+            ang1 = (i == 0) ? sa : gd[i-1][1] + sa;
+            ang2 = gd[i][1] + sa;
+
+            this._sliceAngles.push([ang1, ang2]);
+
+            rprime = calcRPrime(ang1, ang2, this.sliceMargin, this.fill, this.lineWidth);
+
+            if (Math.abs(ang2-ang1) > Math.PI) {
+                maxrprime = Math.max(rprime, maxrprime);  
+            }
+        }
+
+        if (this.diameter != null && this.diameter > 0) {
+            this._diameter = this.diameter - 2*maxrprime;
+        }
+        else {
+            this._diameter = d - 2*maxrprime;
+        }
+
+        // Need to check for undersized pie.  This can happen if
+        // plot area too small and legend is too big.
+        if (this._diameter < 6) {
+            $.jqplot.log('Diameter of pie too small, not rendering.');
+            return;
+        }
+
+        var r = this._radius = this._diameter/2;
+
+        this._center = [(cw - trans * offx)/2 + trans * offx + maxrprime * Math.cos(sa), (ch - trans*offy)/2 + trans * offy + maxrprime * Math.sin(sa)];
+
+        if (this.shadow) {
+            for (var i=0, l=gd.length; i<l; i++) {
+                shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')';
+                this.renderer.drawSlice.call (this, ctx, this._sliceAngles[i][0], this._sliceAngles[i][1], shadowColor, true);
+            }
+        }
+        
+        for (var i=0; i<gd.length; i++) {
+                      
+            this.renderer.drawSlice.call (this, ctx, this._sliceAngles[i][0], this._sliceAngles[i][1], colorGenerator.next(), false);
+        
+            if (this.showDataLabels && gd[i][2]*100 >= this.dataLabelThreshold) {
+                var fstr, avgang = (this._sliceAngles[i][0] + this._sliceAngles[i][1])/2, label;
+            
+                if (this.dataLabels == 'label') {
+                    fstr = this.dataLabelFormatString || '%s';
+                    label = $.jqplot.sprintf(fstr, gd[i][0]);
+                }
+                else if (this.dataLabels == 'value') {
+                    fstr = this.dataLabelFormatString || '%d';
+                    label = $.jqplot.sprintf(fstr, this.data[i][1]);
+                }
+                else if (this.dataLabels == 'percent') {
+                    fstr = this.dataLabelFormatString || '%d%%';
+                    label = $.jqplot.sprintf(fstr, gd[i][2]*100);
+                }
+                else if (this.dataLabels.constructor == Array) {
+                    fstr = this.dataLabelFormatString || '%s';
+                    label = $.jqplot.sprintf(fstr, this.dataLabels[i]);
+                }
+            
+                var fact = (this._radius ) * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
+            
+                var x = this._center[0] + Math.cos(avgang) * fact + this.canvas._offsets.left;
+                var y = this._center[1] + Math.sin(avgang) * fact + this.canvas._offsets.top;
+            
+                var labelelem = $('<div class="jqplot-pie-series jqplot-data-label" style="position:absolute;">' + label + '</div>').insertBefore(plot.eventCanvas._elem);
+                if (this.dataLabelCenterOn) {
+                    x -= labelelem.width()/2;
+                    y -= labelelem.height()/2;
+                }
+                else {
+                    x -= labelelem.width() * Math.sin(avgang/2);
+                    y -= labelelem.height()/2;
+                }
+                x = Math.round(x);
+                y = Math.round(y);
+                labelelem.css({left: x, top: y});
+            }
+        }            
+    };
+    
+    $.jqplot.PieAxisRenderer = function() {
+        $.jqplot.LinearAxisRenderer.call(this);
+    };
+    
+    $.jqplot.PieAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+    $.jqplot.PieAxisRenderer.prototype.constructor = $.jqplot.PieAxisRenderer;
+        
+    
+    // There are no traditional axes on a pie chart.  We just need to provide
+    // dummy objects with properties so the plot will render.
+    // called with scope of axis object.
+    $.jqplot.PieAxisRenderer.prototype.init = function(options){
+        //
+        this.tickRenderer = $.jqplot.PieTickRenderer;
+        $.extend(true, this, options);
+        // I don't think I'm going to need _dataBounds here.
+        // have to go Axis scaling in a way to fit chart onto plot area
+        // and provide u2p and p2u functionality for mouse cursor, etc.
+        // for convienence set _dataBounds to 0 and 100 and
+        // set min/max to 0 and 100.
+        this._dataBounds = {min:0, max:100};
+        this.min = 0;
+        this.max = 100;
+        this.showTicks = false;
+        this.ticks = [];
+        this.showMark = false;
+        this.show = false; 
+    };
+    
+    
+    
+    
+    $.jqplot.PieLegendRenderer = function(){
+        $.jqplot.TableLegendRenderer.call(this);
+    };
+    
+    $.jqplot.PieLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
+    $.jqplot.PieLegendRenderer.prototype.constructor = $.jqplot.PieLegendRenderer;
+    
+    /**
+     * Class: $.jqplot.PieLegendRenderer
+     * Legend Renderer specific to pie plots.  Set by default
+     * when user creates a pie plot.
+     */
+    $.jqplot.PieLegendRenderer.prototype.init = function(options) {
+        // Group: Properties
+        //
+        // prop: numberRows
+        // Maximum number of rows in the legend.  0 or null for unlimited.
+        this.numberRows = null;
+        // prop: numberColumns
+        // Maximum number of columns in the legend.  0 or null for unlimited.
+        this.numberColumns = null;
+        $.extend(true, this, options);
+    };
+    
+    // called with context of legend
+    $.jqplot.PieLegendRenderer.prototype.draw = function() {
+        var legend = this;
+        if (this.show) {
+            var series = this._series;
+
+
+            this._elem = $(document.createElement('table'));
+            this._elem.addClass('jqplot-table-legend');
+
+            var ss = {position:'absolute'};
+            if (this.background) {
+                ss['background'] = this.background;
+            }
+            if (this.border) {
+                ss['border'] = this.border;
+            }
+            if (this.fontSize) {
+                ss['fontSize'] = this.fontSize;
+            }
+            if (this.fontFamily) {
+                ss['fontFamily'] = this.fontFamily;
+            }
+            if (this.textColor) {
+                ss['textColor'] = this.textColor;
+            }
+            if (this.marginTop != null) {
+                ss['marginTop'] = this.marginTop;
+            }
+            if (this.marginBottom != null) {
+                ss['marginBottom'] = this.marginBottom;
+            }
+            if (this.marginLeft != null) {
+                ss['marginLeft'] = this.marginLeft;
+            }
+            if (this.marginRight != null) {
+                ss['marginRight'] = this.marginRight;
+            }
+
+            this._elem.css(ss);
+
+            // Pie charts legends don't go by number of series, but by number of data points
+            // in the series.  Refactor things here for that.
+            
+            var pad = false, 
+                reverse = false,
+                nr, 
+                nc;
+            var s = series[0];
+            var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
+            
+            if (s.show) {
+                var pd = s.data;
+                if (this.numberRows) {
+                    nr = this.numberRows;
+                    if (!this.numberColumns){
+                        nc = Math.ceil(pd.length/nr);
+                    }
+                    else{
+                        nc = this.numberColumns;
+                    }
+                }
+                else if (this.numberColumns) {
+                    nc = this.numberColumns;
+                    nr = Math.ceil(pd.length/this.numberColumns);
+                }
+                else {
+                    nr = pd.length;
+                    nc = 1;
+                }
+                
+                var i, j;
+                var tr, td1, td2; 
+                var lt, rs, color;
+                var idx = 0; 
+                var div0, div1;   
+                
+                for (i=0; i<nr; i++) {
+                    tr = $(document.createElement('tr'));
+                    tr.addClass('jqplot-table-legend');
+                    
+                    if (reverse){
+                        tr.prependTo(this._elem);
+                    }
+                    
+                    else{
+                        tr.appendTo(this._elem);
+                    }
+                    
+                    for (j=0; j<nc; j++) {
+                        if (idx < pd.length){
+                            lt = this.labels[idx] || pd[idx][0].toString();
+                            color = colorGenerator.next();
+                            if (!reverse){
+                                if (i>0){
+                                    pad = true;
+                                }
+                                else{
+                                    pad = false;
+                                }
+                            }
+                            else{
+                                if (i == nr -1){
+                                    pad = false;
+                                }
+                                else{
+                                    pad = true;
+                                }
+                            }
+                            rs = (pad) ? this.rowSpacing : '0';
+
+
+
+                            td1 = $(document.createElement('td'));
+                            td1.addClass('jqplot-table-legend jqplot-table-legend-swatch');
+                            td1.css({textAlign: 'center', paddingTop: rs});
+
+                            div0 = $(document.createElement('div'));
+                            div0.addClass('jqplot-table-legend-swatch-outline');
+                            div1 = $(document.createElement('div'));
+                            div1.addClass('jqplot-table-legend-swatch');
+                            div1.css({backgroundColor: color, borderColor: color});
+                            td1.append(div0.append(div1));
+
+                            td2 = $(document.createElement('td'));
+                            td2.addClass('jqplot-table-legend jqplot-table-legend-label');
+                            td2.css('paddingTop', rs);
+
+                            if (this.escapeHtml){
+                                td2.text(lt);
+                            }
+                            else {
+                                td2.html(lt);
+                            }
+                            if (reverse) {
+                                td2.prependTo(tr);
+                                td1.prependTo(tr);
+                            }
+                            else {
+                                td1.appendTo(tr);
+                                td2.appendTo(tr);
+                            }
+                            pad = true;
+                        }
+                        idx++;
+                    }   
+                }
+            }
+        }
+        return this._elem;                
+    };
+    
+    $.jqplot.PieRenderer.prototype.handleMove = function(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            plot.target.trigger('jqplotDataMouseOver', ins);
+            if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+                plot.target.trigger('jqplotDataHighlight', ins);
+                highlight (plot, ins[0], ins[1]);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    };
+    
+    
+    // this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]);
+    
+    // setup default renderers for axes and legend so user doesn't have to
+    // called with scope of plot
+    function preInit(target, data, options) {
+        options = options || {};
+        options.axesDefaults = options.axesDefaults || {};
+        options.legend = options.legend || {};
+        options.seriesDefaults = options.seriesDefaults || {};
+        // only set these if there is a pie series
+        var setopts = false;
+        if (options.seriesDefaults.renderer == $.jqplot.PieRenderer) {
+            setopts = true;
+        }
+        else if (options.series) {
+            for (var i=0; i < options.series.length; i++) {
+                if (options.series[i].renderer == $.jqplot.PieRenderer) {
+                    setopts = true;
+                }
+            }
+        }
+        
+        if (setopts) {
+            options.axesDefaults.renderer = $.jqplot.PieAxisRenderer;
+            options.legend.renderer = $.jqplot.PieLegendRenderer;
+            options.legend.preDraw = true;
+            options.seriesDefaults.pointLabels = {show: false};
+        }
+    }
+    
+    function postInit(target, data, options) {
+        for (var i=0; i<this.series.length; i++) {
+            if (this.series[i].renderer.constructor == $.jqplot.PieRenderer) {
+                // don't allow mouseover and mousedown at same time.
+                if (this.series[i].highlightMouseOver) {
+                    this.series[i].highlightMouseDown = false;
+                }
+            }
+        }
+    }
+    
+    // called with scope of plot
+    function postParseOptions(options) {
+        for (var i=0; i<this.series.length; i++) {
+            this.series[i].seriesColors = this.seriesColors;
+            this.series[i].colorGenerator = $.jqplot.colorGenerator;
+        }
+    }
+    
+    function highlight (plot, sidx, pidx) {
+        var s = plot.series[sidx];
+        var canvas = plot.plugins.pieRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        s._highlightedPoint = pidx;
+        plot.plugins.pieRenderer.highlightedSeriesIndex = sidx;
+        s.renderer.drawSlice.call(s, canvas._ctx, s._sliceAngles[pidx][0], s._sliceAngles[pidx][1], s.highlightColorGenerator.get(pidx), false);
+    }
+    
+    function unhighlight (plot) {
+        var canvas = plot.plugins.pieRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        for (var i=0; i<plot.series.length; i++) {
+            plot.series[i]._highlightedPoint = null;
+        }
+        plot.plugins.pieRenderer.highlightedSeriesIndex = null;
+        plot.target.trigger('jqplotDataUnhighlight');
+    }
+ 
+    function handleMove(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var evt1 = jQuery.Event('jqplotDataMouseOver');
+            evt1.pageX = ev.pageX;
+            evt1.pageY = ev.pageY;
+            plot.target.trigger(evt1, ins);
+            if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, ins[0], ins[1]);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    } 
+    
+    function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, ins[0], ins[1]);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    }
+    
+    function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
+        var idx = plot.plugins.pieRenderer.highlightedSeriesIndex;
+        if (idx != null && plot.series[idx].highlightMouseDown) {
+            unhighlight(plot);
+        }
+    }
+    
+    function handleClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var evt = jQuery.Event('jqplotDataClick');
+            evt.which = ev.which;
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            plot.target.trigger(evt, ins);
+        }
+    }
+    
+    function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var idx = plot.plugins.pieRenderer.highlightedSeriesIndex;
+            if (idx != null && plot.series[idx].highlightMouseDown) {
+                unhighlight(plot);
+            }
+            var evt = jQuery.Event('jqplotDataRightClick');
+            evt.which = ev.which;
+            evt.pageX = ev.pageX;
+            evt.pageY = ev.pageY;
+            plot.target.trigger(evt, ins);
+        }
+    }    
+    
+    // called within context of plot
+    // create a canvas which we can draw on.
+    // insert it before the eventCanvas, so eventCanvas will still capture events.
+    function postPlotDraw() {
+        // Memory Leaks patch    
+        if (this.plugins.pieRenderer && this.plugins.pieRenderer.highlightCanvas) {
+            this.plugins.pieRenderer.highlightCanvas.resetCanvas();
+            this.plugins.pieRenderer.highlightCanvas = null;
+        }
+
+        this.plugins.pieRenderer = {highlightedSeriesIndex:null};
+        this.plugins.pieRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
+        
+        // do we have any data labels?  if so, put highlight canvas before those
+        var labels = $(this.targetId+' .jqplot-data-label');
+        if (labels.length) {
+            $(labels[0]).before(this.plugins.pieRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-pieRenderer-highlight-canvas', this._plotDimensions, this));
+        }
+        // else put highlight canvas before event canvas.
+        else {
+            this.eventCanvas._elem.before(this.plugins.pieRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-pieRenderer-highlight-canvas', this._plotDimensions, this));
+        }
+        
+        var hctx = this.plugins.pieRenderer.highlightCanvas.setContext();
+        this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
+    }
+    
+    $.jqplot.preInitHooks.push(preInit);
+    
+    $.jqplot.PieTickRenderer = function() {
+        $.jqplot.AxisTickRenderer.call(this);
+    };
+    
+    $.jqplot.PieTickRenderer.prototype = new $.jqplot.AxisTickRenderer();
+    $.jqplot.PieTickRenderer.prototype.constructor = $.jqplot.PieTickRenderer;
+    
+})(jQuery);
+    
+    
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pointLabels.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pointLabels.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pointLabels.js	(revision 2119)
@@ -0,0 +1,377 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    
+    /**
+     * Class: $.jqplot.PointLabels
+     * Plugin for putting labels at the data points.
+     * 
+     * To use this plugin, include the js
+     * file in your source:
+     * 
+     * > <script type="text/javascript" src="plugins/jqplot.pointLabels.js"></script>
+     * 
+     * By default, the last value in the data ponit array in the data series is used
+     * for the label.  For most series renderers, extra data can be added to the 
+     * data point arrays and the last value will be used as the label.
+     * 
+     * For instance, 
+     * this series:
+     * 
+     * > [[1,4], [3,5], [7,2]]
+     * 
+     * Would, by default, use the y values in the labels.
+     * Extra data can be added to the series like so:
+     * 
+     * > [[1,4,'mid'], [3 5,'hi'], [7,2,'low']]
+     * 
+     * And now the point labels would be 'mid', 'low', and 'hi'.
+     * 
+     * Options to the point labels and a custom labels array can be passed into the
+     * "pointLabels" option on the series option like so:
+     * 
+     * > series:[{pointLabels:{
+     * >    labels:['mid', 'hi', 'low'],
+     * >    location:'se',
+     * >    ypadding: 12
+     * >    }
+     * > }]
+     * 
+     * A custom labels array in the options takes precendence over any labels
+     * in the series data.  If you have a custom labels array in the options,
+     * but still want to use values from the series array as labels, set the
+     * "labelsFromSeries" option to true.
+     * 
+     * By default, html entities (<, >, etc.) are escaped in point labels.  
+     * If you want to include actual html markup in the labels, 
+     * set the "escapeHTML" option to false.
+     * 
+     */
+    $.jqplot.PointLabels = function(options) {
+        // Group: Properties
+        //
+        // prop: show
+        // show the labels or not.
+        this.show = $.jqplot.config.enablePlugins;
+        // prop: location
+        // compass location where to position the label around the point.
+        // 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
+        this.location = 'n';
+        // prop: labelsFromSeries
+        // true to use labels within data point arrays.
+        this.labelsFromSeries = false;
+        // prop: seriesLabelIndex
+        // array index for location of labels within data point arrays.
+        // if null, will use the last element of the data point array.
+        this.seriesLabelIndex = null;
+        // prop: labels
+        // array of arrays of labels, one array for each series.
+        this.labels = [];
+        // actual labels that will get displayed.
+        // needed to preserve user specified labels in labels array.
+        this._labels = [];
+        // prop: stackedValue
+        // true to display value as stacked in a stacked plot.
+        // no effect if labels is specified.
+        this.stackedValue = false;
+        // prop: ypadding
+        // vertical padding in pixels between point and label
+        this.ypadding = 6;
+        // prop: xpadding
+        // horizontal padding in pixels between point and label
+        this.xpadding = 6;
+        // prop: escapeHTML
+        // true to escape html entities in the labels.
+        // If you want to include markup in the labels, set to false.
+        this.escapeHTML = true;
+        // prop: edgeTolerance
+        // Number of pixels that the label must be away from an axis
+        // boundary in order to be drawn.  Negative values will allow overlap
+        // with the grid boundaries.
+        this.edgeTolerance = -5;
+        // prop: formatter
+        // A class of a formatter for the tick text.  sprintf by default.
+        this.formatter = $.jqplot.DefaultTickFormatter;
+        // prop: formatString
+        // string passed to the formatter.
+        this.formatString = '';
+        // prop: hideZeros
+        // true to not show a label for a value which is 0.
+        this.hideZeros = false;
+        this._elems = [];
+        
+        $.extend(true, this, options);
+    };
+    
+    var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
+    var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
+    var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
+    
+    // called with scope of a series
+    $.jqplot.PointLabels.init = function (target, data, seriesDefaults, opts, plot){
+        var options = $.extend(true, {}, seriesDefaults, opts);
+        options.pointLabels = options.pointLabels || {};
+        if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal' && !options.pointLabels.location) {
+            options.pointLabels.location = 'e';
+        }
+        // add a pointLabels attribute to the series plugins
+        this.plugins.pointLabels = new $.jqplot.PointLabels(options.pointLabels);
+        this.plugins.pointLabels.setLabels.call(this);
+    };
+    
+    // called with scope of series
+    $.jqplot.PointLabels.prototype.setLabels = function() {   
+        var p = this.plugins.pointLabels; 
+        var labelIdx;
+        if (p.seriesLabelIndex != null) {
+            labelIdx = p.seriesLabelIndex;
+        }
+        else if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal') {
+           labelIdx = (this._plotData[0].length < 3) ? 0 : this._plotData[0].length -1;
+        }
+        else {
+            labelIdx = (this._plotData.length === 0) ? 0 : this._plotData[0].length -1;
+        }
+        p._labels = [];
+        if (p.labels.length === 0 || p.labelsFromSeries) {    
+            if (p.stackedValue) {
+                if (this._plotData.length && this._plotData[0].length){
+                    // var idx = p.seriesLabelIndex || this._plotData[0].length -1;
+                    for (var i=0; i<this._plotData.length; i++) {
+                        p._labels.push(this._plotData[i][labelIdx]);
+                    }
+                }
+            }
+            else {
+                // var d = this._plotData;
+                var d = this.data;
+                if (this.renderer.constructor === $.jqplot.BarRenderer && this.waterfall) {
+                    d = this._data;
+                }
+                if (d.length && d[0].length) {
+                    // var idx = p.seriesLabelIndex || d[0].length -1;
+                    for (var i=0; i<d.length; i++) {
+                        p._labels.push(d[i][labelIdx]);
+                    }
+                }
+                d = null;
+            }
+        }
+        else if (p.labels.length){
+            p._labels = p.labels;
+        }
+    };
+    
+    $.jqplot.PointLabels.prototype.xOffset = function(elem, location, padding) {
+        location = location || this.location;
+        padding = padding || this.xpadding;
+        var offset;
+        
+        switch (location) {
+            case 'nw':
+                offset = -elem.outerWidth(true) - this.xpadding;
+                break;
+            case 'n':
+                offset = -elem.outerWidth(true)/2;
+                break;
+            case 'ne':
+                offset =  this.xpadding;
+                break;
+            case 'e':
+                offset = this.xpadding;
+                break;
+            case 'se':
+                offset = this.xpadding;
+                break;
+            case 's':
+                offset = -elem.outerWidth(true)/2;
+                break;
+            case 'sw':
+                offset = -elem.outerWidth(true) - this.xpadding;
+                break;
+            case 'w':
+                offset = -elem.outerWidth(true) - this.xpadding;
+                break;
+            default: // same as 'nw'
+                offset = -elem.outerWidth(true) - this.xpadding;
+                break;
+        }
+        return offset; 
+    };
+    
+    $.jqplot.PointLabels.prototype.yOffset = function(elem, location, padding) {
+        location = location || this.location;
+        padding = padding || this.xpadding;
+        var offset;
+        
+        switch (location) {
+            case 'nw':
+                offset = -elem.outerHeight(true) - this.ypadding;
+                break;
+            case 'n':
+                offset = -elem.outerHeight(true) - this.ypadding;
+                break;
+            case 'ne':
+                offset = -elem.outerHeight(true) - this.ypadding;
+                break;
+            case 'e':
+                offset = -elem.outerHeight(true)/2;
+                break;
+            case 'se':
+                offset = this.ypadding;
+                break;
+            case 's':
+                offset = this.ypadding;
+                break;
+            case 'sw':
+                offset = this.ypadding;
+                break;
+            case 'w':
+                offset = -elem.outerHeight(true)/2;
+                break;
+            default: // same as 'nw'
+                offset = -elem.outerHeight(true) - this.ypadding;
+                break;
+        }
+        return offset; 
+    };
+    
+    // called with scope of series
+    $.jqplot.PointLabels.draw = function (sctx, options, plot) {
+        var p = this.plugins.pointLabels;
+        // set labels again in case they have changed.
+        p.setLabels.call(this);
+        // remove any previous labels
+        for (var i=0; i<p._elems.length; i++) {
+            // Memory Leaks patch
+            // p._elems[i].remove();
+            p._elems[i].emptyForce();
+        }
+        p._elems.splice(0, p._elems.length);
+
+        if (p.show) {
+            var ax = '_'+this._stackAxis+'axis';
+        
+            if (!p.formatString) {
+                p.formatString = this[ax]._ticks[0].formatString;
+                p.formatter = this[ax]._ticks[0].formatter;
+            }
+        
+            var pd = this._plotData;
+            var ppd = this._prevPlotData;
+            var xax = this._xaxis;
+            var yax = this._yaxis;
+            var elem, helem;
+
+            for (var i=0, l=p._labels.length; i < l; i++) {
+                var label = p._labels[i];
+                
+                if (label == null || (p.hideZeros && parseInt(label, 10) == 0)) {
+                    continue;
+                }
+                
+                label = p.formatter(p.formatString, label);
+
+                helem = document.createElement('div');
+                p._elems[i] = $(helem);
+
+                elem = p._elems[i];
+
+
+                elem.addClass('jqplot-point-label jqplot-series-'+this.index+' jqplot-point-'+i);
+                elem.css('position', 'absolute');
+                elem.insertAfter(sctx.canvas);
+
+                if (p.escapeHTML) {
+                    elem.text(label);
+                }
+                else {
+                    elem.html(label);
+                }
+                var location = p.location;
+                if ((this.fillToZero && pd[i][1] < 0) || (this.fillToZero && this._type === 'bar' && this.barDirection === 'horizontal' && pd[i][0] < 0) || (this.waterfall && parseInt(label, 10)) < 0) {
+                    location = oppositeLocations[locationIndicies[location]];
+                }
+
+
+                var ell = xax.u2p(pd[i][0]) + p.xOffset(elem, location);
+                var elt = yax.u2p(pd[i][1]) + p.yOffset(elem, location);
+
+                // we have stacked chart but are not showing stacked values,
+                // place labels in center.
+                if (this._stack && !p.stackedValue) {
+                    if (this.barDirection === "vertical") {
+                        elt = (this._barPoints[i][0][1] + this._barPoints[i][1][1]) / 2 + plot._gridPadding.top - 0.5 * elem.outerHeight(true);
+                    }
+                    else {
+                        ell = (this._barPoints[i][2][0] + this._barPoints[i][0][0]) / 2 + plot._gridPadding.left - 0.5 * elem.outerWidth(true);
+                    }
+                }
+
+                if (this.renderer.constructor == $.jqplot.BarRenderer) {
+                    if (this.barDirection == "vertical") {
+                        ell += this._barNudge;
+                    }
+                    else {
+                        elt -= this._barNudge;
+                    }
+                }
+                elem.css('left', ell);
+                elem.css('top', elt);
+                var elr = ell + elem.width();
+                var elb = elt + elem.height();
+                var et = p.edgeTolerance;
+                var scl = $(sctx.canvas).position().left;
+                var sct = $(sctx.canvas).position().top;
+                var scr = sctx.canvas.width + scl;
+                var scb = sctx.canvas.height + sct;
+                // if label is outside of allowed area, remove it
+                if (ell - et < scl || elt - et < sct || elr + et > scr || elb + et > scb) {
+                    elem.remove();
+                }
+
+                elem = null;
+                helem = null;
+            }
+
+            // finally, animate them if the series is animated
+            // if (this.renderer.animation && this.renderer.animation._supported && this.renderer.animation.show && plot._drawCount < 2) {
+            //     var sel = '.jqplot-point-label.jqplot-series-'+this.index;
+            //     $(sel).hide();
+            //     $(sel).fadeIn(1000);
+            // }
+
+        }
+    };
+    
+    $.jqplot.postSeriesInitHooks.push($.jqplot.PointLabels.init);
+    $.jqplot.postDrawSeriesHooks.push($.jqplot.PointLabels.draw);
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pyramidAxisRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pyramidAxisRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pyramidAxisRenderer.js	(revision 2119)
@@ -0,0 +1,728 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    $.jqplot.PyramidAxisRenderer = function() {
+        $.jqplot.LinearAxisRenderer.call(this);
+    };
+    
+    $.jqplot.PyramidAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
+    $.jqplot.PyramidAxisRenderer.prototype.constructor = $.jqplot.PyramidAxisRenderer;
+        
+    // called with scope of axis
+    $.jqplot.PyramidAxisRenderer.prototype.init = function(options){
+        // Group: Properties
+        //
+        // prop: position
+        // Position of axis.  Values are: top, bottom , left, center, right.
+        // By default, x and x2 axes are bottom, y axis is center.
+        this.position = null;
+        // prop: drawBaseline
+        // True to draw the axis baseline.
+        this.drawBaseline = true;
+        // prop: baselineWidth
+        // width of the baseline in pixels.
+        this.baselineWidth = null;
+        // prop: baselineColor
+        // CSS color spec for the baseline.
+        this.baselineColor = null;
+        this.tickSpacingFactor = 25;
+        this._type = 'pyramid';
+        this._splitAxis = false;
+        this._splitLength = null;
+        this.category = false;
+        this._autoFormatString = '';
+        this._overrideFormatString = false;
+        
+        $.extend(true, this, options);
+        this.renderer.options = options;
+
+        this.resetDataBounds = this.renderer.resetDataBounds;
+        this.resetDataBounds();
+
+    };
+
+    $.jqplot.PyramidAxisRenderer.prototype.resetDataBounds = function() {
+        // Go through all the series attached to this axis and find
+        // the min/max bounds for this axis.
+        var db = this._dataBounds;
+        db.min = null;
+        db.max = null;
+        var temp;
+        for (var i=0; i<this._series.length; i++) {
+            var s = this._series[i];
+            var d = s._plotData;
+            
+            for (var j=0, l=d.length; j<l; j++) { 
+                if (this.name.charAt(0) === 'x') {
+                    temp = d[j][1];
+                    if ((temp !== null && temp < db.min) || db.min === null) {
+                        db.min = temp;
+                    }
+                    if ((temp !== null && temp > db.max) || db.max === null) {
+                        db.max = temp;
+                    }
+                }              
+                else {
+                    temp = d[j][0];
+                    if ((temp !== null && temp < db.min) || db.min === null) {
+                        db.min = temp;
+                    }
+                    if ((temp !== null && temp > db.max) || db.max === null) {
+                        db.max = temp;
+                    }
+                }              
+            }
+        }
+    };
+    
+    // called with scope of axis
+    $.jqplot.PyramidAxisRenderer.prototype.draw = function(ctx, plot) {
+        if (this.show) {
+            // populate the axis label and value properties.
+            // createTicks is a method on the renderer, but
+            // call it within the scope of the axis.
+            this.renderer.createTicks.call(this, plot);
+            // fill a div with axes labels in the right direction.
+            // Need to pregenerate each axis to get its bounds and
+            // position it and the labels correctly on the plot.
+            var dim=0;
+            var temp;
+            // Added for theming.
+            if (this._elem) {
+                // Memory Leaks patch
+                //this._elem.empty();
+                this._elem.emptyForce();
+                this._elem = null;
+            }
+            
+            this._elem = $(document.createElement('div'));
+            this._elem.addClass('jqplot-axis jqplot-'+this.name);
+            this._elem.css('position', 'absolute');
+
+            
+            if (this.name == 'xaxis' || this.name == 'x2axis') {
+                this._elem.width(this._plotDimensions.width);
+            }
+            else {
+                this._elem.height(this._plotDimensions.height);
+            }
+            
+            // create a _label object.
+            this.labelOptions.axis = this.name;
+            this._label = new this.labelRenderer(this.labelOptions);
+            if (this._label.show) {
+                var elem = this._label.draw(ctx, plot);
+                elem.appendTo(this._elem);
+                elem = null;
+            }
+    
+            var t = this._ticks;
+            var tick;
+            for (var i=0; i<t.length; i++) {
+                tick = t[i];
+                if (tick.show && tick.showLabel && (!tick.isMinorTick)) {
+                    this._elem.append(tick.draw(ctx, plot));
+                }
+            }
+            tick = null;
+            t = null;
+        }
+        return this._elem;
+    };   
+
+    // Note, primes can be found on http://primes.utm.edu/
+    var _primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997];
+
+
+    var _primesHash = {};
+
+    for (var i =0, l = _primes.length; i < l; i++) {
+        _primesHash[_primes[i]] = _primes[i];
+    }
+
+    // called with scope of axis
+    $.jqplot.PyramidAxisRenderer.prototype.createTicks = function(plot) {
+        // we're are operating on an axis here
+        var userTicks = this.ticks;
+        // databounds were set on axis initialization.
+        var db = this._dataBounds;
+        var dim;
+        var interval;
+        var min;
+        var max;
+        var range;
+        var pos1;
+        var pos2;
+        var tt;
+        var i;
+        var l;
+        var s;
+        // get a copy of user's settings for min/max.
+        var userMin = this.min;
+        var userMax = this.max;
+        var ut;
+        var t;
+        var threshold;
+        var tdim;
+        var scalefact;
+        var ret;
+        var tumin;
+        var tumax;
+        var maxVisibleTicks;
+        var val;
+        var skip = null;
+        var temp;
+        
+        // if we already have ticks, use them.
+        // ticks must be in order of increasing value.
+
+        if (userTicks.length) {
+            // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
+            for (i=0, l=userTicks.length; i<l; i++){
+                ut = userTicks[i];
+                t = new this.tickRenderer(this.tickOptions);
+                if ($.isArray(ut)) {
+                    t.value = ut[0];
+                    t.label = ut[1];
+                    t.setTick(ut[0], this.name);
+                    this._ticks.push(t);
+                }
+
+                else if ($.isPlainObject(ut)) {
+                    $.extend(true, t, ut);
+                    t.axis = this.name;
+                    this._ticks.push(t);
+                }
+                
+                else {
+                    if (typeof ut === 'string') {
+                        val = i + plot.defaultAxisStart;
+                    }
+                    else {
+                        val = ut;
+                    }
+                    t.value = val;
+                    t.label = ut;
+                    t.axis = this.name;
+                    this._ticks.push(t);
+                }
+            }
+            this.numberTicks = userTicks.length;
+            this.min = this._ticks[0].value;
+            this.max = this._ticks[this.numberTicks-1].value;
+            this.tickInterval = (this.max - this.min) / (this.numberTicks - 1);
+
+            // use user specified tickInterval if there is one
+            if (this._options.tickInterval) {
+                // hide every tick except for ticks on interval
+                var ti = this._options.tickInterval;
+                for (i=0; i<this.numberTicks; i++) {
+                    if (i%ti !== 0) {
+                        // this._ticks[i].show = false;
+                        this._ticks[i].isMinorTick = true;
+                    }
+                }
+            }
+
+            else {
+                // check if we have too many ticks
+                dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
+                maxVisibleTicks = Math.round(2.0 + dim/this.tickSpacingFactor);
+
+                if (this.numberTicks > maxVisibleTicks) {
+                    // check for number of ticks we can skip
+                    temp = this.numberTicks - 1;
+                    for (i=2; i<temp; i++) {
+                        if (temp % i === 0 && temp/i < maxVisibleTicks) {
+                            skip = i-1;
+                            break;
+                        }
+                    }
+
+                    if (skip !== null) {
+                        var count = 1;
+                        for (i=1, l=this._ticks.length; i<l; i++) {
+                            if (count <= skip) {
+                                this._ticks[i].show = false;
+                                count += 1;
+                            }
+                            else {
+                                count = 1;
+                            }
+                        }
+                    }
+                }
+            }
+
+            // if category style, add minor ticks in between
+            temp = [];
+            if (this.category) {
+                // turn off gridline and mark on first tick
+                this._ticks[0].showGridline = false;
+                this._ticks[0].showMark = false;
+
+                for (i=this._ticks.length-1; i>0; i--) {
+                    t = new this.tickRenderer(this.tickOptions);
+                    t.value = this._ticks[i-1].value + this.tickInterval/2.0;
+                    t.label = '';
+                    t.showLabel = false;
+                    t.axis = this.name;
+                    this._ticks[i].showGridline = false;
+                    this._ticks[i].showMark = false;
+                    this._ticks.splice(i, 0, t);
+                    // temp.push(t);
+                }
+
+                // merge in the new ticks
+                // for (i=1, l=temp.length; i<l; i++) {
+                //     this._ticks.splice(i, 0, temp[i]);
+                // }
+
+                // now add a tick at beginning and end
+                t = new this.tickRenderer(this.tickOptions);
+                t.value = this._ticks[0].value - this.tickInterval/2.0;
+                t.label = '';
+                t.showLabel = false;
+                t.axis = this.name;
+                this._ticks.unshift(t);
+
+                t = new this.tickRenderer(this.tickOptions);
+                t.value = this._ticks[this._ticks.length-1].value + this.tickInterval/2.0;
+                t.label = '';
+                t.showLabel = false;
+                t.axis = this.name;
+                this._ticks.push(t);
+
+                this.tickInterval = this.tickInterval / 2.0;
+                this.numberTicks = this._ticks.length;
+                this.min = this._ticks[0].value;
+                this.max = this._ticks[this._ticks.length-1].value;
+            }
+        }
+
+        // we don't have any ticks yet, let's make some!
+        else {
+            if (this.name.charAt(0) === 'x') {
+                dim = this._plotDimensions.width;
+                // make sure x axis is symetric about 0.
+                var tempmax = Math.max(db.max, Math.abs(db.min));
+                var tempmin = Math.min(db.min, -tempmax);
+                // min = ((this.min != null) ? this.min : tempmin);
+                // max = ((this.max != null) ? this.max : tempmax);
+                min = tempmin;
+                max = tempmax;
+                range = max - min;
+
+                if (this.tickOptions == null || !this.tickOptions.formatString) {
+                    this._overrideFormatString = true;
+                }
+
+                threshold = 30;
+                tdim = Math.max(dim, threshold+1);
+                scalefact =  (tdim-threshold)/300.0;
+                ret = $.jqplot.LinearTickGenerator(min, max, scalefact); 
+                // calculate a padded max and min, points should be less than these
+                // so that they aren't too close to the edges of the plot.
+                // User can adjust how much padding is allowed with pad, padMin and PadMax options. 
+                tumin = min + range*(this.padMin - 1);
+                tumax = max - range*(this.padMax - 1);
+
+                if (min < tumin || max > tumax) {
+                    tumin = min - range*(this.padMin - 1);
+                    tumax = max + range*(this.padMax - 1);
+                    ret = $.jqplot.LinearTickGenerator(tumin, tumax, scalefact);
+                }
+
+                this.min = ret[0];
+                this.max = ret[1];
+                this.numberTicks = ret[2];
+                this._autoFormatString = ret[3];
+                this.tickInterval = ret[4];
+            }
+            else {
+                dim = this._plotDimensions.height;
+
+                // ticks will be on whole integers like 1, 2, 3, ... or 1, 4, 7, ...
+                min = db.min;
+                max = db.max;
+                s = this._series[0];
+                this._ticks = [];
+
+                range = max - min;
+
+                // if range is a prime, will get only 2 ticks, expand range in that case.
+                if (_primesHash[range]) {
+                    range += 1;
+                    max += 1;
+                }
+
+                this.max = max;
+                this.min = min;
+                
+                maxVisibleTicks = Math.round(2.0 + dim/this.tickSpacingFactor);
+
+                if (range + 1 <= maxVisibleTicks) {
+                    this.numberTicks = range + 1;
+                    this.tickInterval = 1.0;
+                }
+
+                else {
+                    // figure out a round number of ticks to skip in every interval
+                    // range / ti + 1 = nt
+                    // ti = range / (nt - 1)
+                    for (var i=maxVisibleTicks; i>1; i--) {
+                        if (range/(i - 1) === Math.round(range/(i - 1))) {
+                            this.numberTicks = i;
+                            this.tickInterval = range/(i - 1);
+                            break;
+                        }
+                        
+                    }
+                }
+            }
+            
+            if (this._overrideFormatString && this._autoFormatString != '') {
+                this.tickOptions = this.tickOptions || {};
+                this.tickOptions.formatString = this._autoFormatString;
+            }
+
+            var labelval;
+            for (i=0; i<this.numberTicks; i++) {
+                this.tickOptions.axis = this.name;
+                labelval = this.min + this.tickInterval * i;
+                if (this.name.charAt(0) === 'x') {
+                    labelval = Math.abs(labelval);
+                }
+                // this.tickOptions.label = String (labelval);
+                this.tickOptions.value = this.min + this.tickInterval * i;
+                t = new this.tickRenderer(this.tickOptions);
+
+                t.label = t.prefix + t.formatter(t.formatString, labelval);
+
+                this._ticks.push(t);
+                // for x axis, if y axis is in middle, add a symetrical 0 tick
+                if (this.name.charAt(0) === 'x' && plot.axes.yMidAxis.show && this.tickOptions.value === 0) {
+                    this._splitAxis = true;
+                    this._splitLength = plot.axes.yMidAxis.getWidth();
+                    // t.value = -this.max/2000.0;
+                    t = new this.tickRenderer(this.tickOptions);
+                    this._ticks.push(t);
+                    t.value = this.max/2000.0;
+                }
+            }
+            t = null;
+        }
+    };
+    
+    // called with scope of axis
+    $.jqplot.PyramidAxisRenderer.prototype.set = function() { 
+        var dim = 0;
+        var temp;
+        var w = 0;
+        var h = 0;
+        var i;
+        var t;
+        var tick;
+        var lshow = (this._label == null) ? false : this._label.show;
+        if (this.show) {
+            t = this._ticks;
+            l = t.length;
+            for (i=0; i<l; i++) {
+                tick = t[i];
+                if (!tick._breakTick && tick.show && tick.showLabel && !tick.isMinorTick) {
+                    if (this.name.charAt(0) === 'x') {
+                        temp = tick._elem.outerHeight(true);
+                    }
+                    else {
+                        temp = tick._elem.outerWidth(true);
+                    }
+                    if (temp > dim) {
+                        dim = temp;
+                    }
+                }
+            }
+
+            if (this.name === 'yMidAxis') {
+                for (i=0; i<l; i++) {
+                    tick = t[i];
+                    if (tick._elem) {
+                        temp = (dim - tick._elem.outerWidth(true))/2.0;
+                        tick._elem.css('left', temp);
+                    }
+                }
+            }
+            tick = null;
+            t = null;
+            
+            if (lshow) {
+                w = this._label._elem.outerWidth(true);
+                h = this._label._elem.outerHeight(true); 
+            }
+            if (this.name === 'xaxis') {
+                dim = dim + h;
+                this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
+            }
+            else if (this.name === 'x2axis') {
+                dim = dim + h;
+                this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
+            }
+            else if (this.name === 'yaxis') {
+                dim = dim + w;
+                this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
+                if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+                    this._label._elem.css('width', w+'px');
+                }
+            }
+            else if (this.name === 'yMidAxis') {
+                // don't include width of label at all in width of axis?
+                // dim = (dim > w) ? dim : w;
+                var temp = dim/2.0 - w/2.0;
+                this._elem.css({'width':dim+'px', top:'0px'});
+                if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+                    this._label._elem.css({width: w, left: temp, top: 0});
+                }
+            }
+            else {
+                dim = dim + w;
+                this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
+                if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
+                    this._label._elem.css('width', w+'px');
+                }
+            }
+        }  
+    };
+    
+    $.jqplot.PyramidAxisRenderer.prototype.pack = function(pos, offsets) { 
+        // Add defaults for repacking from resetTickValues function.
+        pos = pos || {};
+        offsets = offsets || this._offsets;
+        
+        var ticks = this._ticks;
+        var max = this.max;
+        var min = this.min;
+        var offmax = offsets.max;
+        var offmin = offsets.min;
+        var lshow = (this._label == null) ? false : this._label.show;
+        
+        for (var p in pos) {
+            this._elem.css(p, pos[p]);
+        }
+        
+        this._offsets = offsets;
+        // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
+        var pixellength = offmax - offmin;
+        var unitlength = max - min;
+        var sl = this._splitLength;
+        
+        // point to unit and unit to point conversions references to Plot DOM element top left corner.
+        if (this._splitAxis) {
+            pixellength -= this._splitLength;
+            
+            // don't know that this one is correct.
+            this.p2u = function(p){
+                return (p - offmin) * unitlength / pixellength + min;
+            };
+        
+            this.u2p = function(u){
+                if (u <= 0) {
+                    return (u - min) * pixellength / unitlength + offmin;
+                }
+                else {
+                    return (u - min) * pixellength / unitlength + offmin + sl;
+                }
+            };
+                
+            this.series_u2p = function(u){
+                if (u <= 0) {
+                    return (u - min) * pixellength / unitlength;
+                }
+                else {
+                    return (u - min) * pixellength / unitlength + sl;
+                }
+            };
+
+            // don't know that this one is correct.
+            this.series_p2u = function(p){
+                return p * unitlength / pixellength + min;
+            };
+        }
+        else {
+            this.p2u = function(p){
+                return (p - offmin) * unitlength / pixellength + min;
+            };
+        
+            this.u2p = function(u){
+                return (u - min) * pixellength / unitlength + offmin;
+            };
+                
+            if (this.name.charAt(0) === 'x'){
+                this.series_u2p = function(u){
+                    return (u - min) * pixellength / unitlength;
+                };
+                this.series_p2u = function(p){
+                    return p * unitlength / pixellength + min;
+                };
+            }
+        
+            else {
+                this.series_u2p = function(u){
+                    return (u - max) * pixellength / unitlength;
+                };
+                this.series_p2u = function(p){
+                    return p * unitlength / pixellength + max;
+                };
+            }
+        }
+        
+        if (this.show) {
+            if (this.name.charAt(0) === 'x') {
+                for (var i=0; i<ticks.length; i++) {
+                    var t = ticks[i];
+                    if (t.show && t.showLabel) {
+                        var shim;
+                        
+                        if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+                            // will need to adjust auto positioning based on which axis this is.
+                            var temp = (this.name == 'xaxis') ? 1 : -1;
+                            switch (t.labelPosition) {
+                                case 'auto':
+                                    // position at end
+                                    if (temp * t.angle < 0) {
+                                        shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    }
+                                    // position at start
+                                    else {
+                                        shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'end':
+                                    shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                                case 'start':
+                                    shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    break;
+                                case 'middle':
+                                    shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                                default:
+                                    shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    break;
+                            }
+                        }
+                        else {
+                            shim = -t.getWidth()/2;
+                        }
+                        var val = this.u2p(t.value) + shim + 'px';
+                        t._elem.css('left', val);
+                        t.pack();
+                    }
+                }
+                if (lshow) {
+                    var w = this._label._elem.outerWidth(true);
+                    this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
+                    if (this.name == 'xaxis') {
+                        this._label._elem.css('bottom', '0px');
+                    }
+                    else {
+                        this._label._elem.css('top', '0px');
+                    }
+                    this._label.pack();
+                }
+            }
+            else {
+                for (var i=0; i<ticks.length; i++) {
+                    var t = ticks[i];
+                    if (t.show && t.showLabel && !t.isMinorTick) {                        
+                        var shim;
+                        if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
+                            // will need to adjust auto positioning based on which axis this is.
+                            var temp = (this.name == 'yaxis') ? 1 : -1;
+                            switch (t.labelPosition) {
+                                case 'auto':
+                                    // position at end
+                                case 'end':
+                                    if (temp * t.angle < 0) {
+                                        shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+                                    }
+                                    else {
+                                        shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'start':
+                                    if (t.angle > 0) {
+                                        shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
+                                    }
+                                    else {
+                                        shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
+                                    }
+                                    break;
+                                case 'middle':
+                                    // if (t.angle > 0) {
+                                    //     shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
+                                    // }
+                                    // else {
+                                    //     shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
+                                    // }
+                                    shim = -t.getHeight()/2;
+                                    break;
+                                default:
+                                    shim = -t.getHeight()/2;
+                                    break;
+                            }
+                        }
+                        else {
+                            shim = -t.getHeight()/2;
+                        }
+                        
+                        var val = this.u2p(t.value) + shim + 'px';
+                        t._elem.css('top', val);
+                        t.pack();
+                    }
+                }
+                if (lshow) {
+                    var h = this._label._elem.outerHeight(true);
+                    if (this.name !== 'yMidAxis') {
+                        this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
+                    }
+                    if (this.name == 'yaxis') {
+                        this._label._elem.css('left', '0px');
+                    }
+                    else if (this.name !== 'yMidAxis') {
+                        this._label._elem.css('right', '0px');
+                    }   
+                    this._label.pack();
+                }
+            }
+        }
+
+        ticks = null;
+    };
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pyramidGridRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pyramidGridRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pyramidGridRenderer.js	(revision 2119)
@@ -0,0 +1,429 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {     
+    // Class: $.jqplot.CanvasGridRenderer
+    // The default jqPlot grid renderer, creating a grid on a canvas element.
+    // The renderer has no additional options beyond the <Grid> class.
+    $.jqplot.PyramidGridRenderer = function(){
+        $.jqplot.CanvasGridRenderer.call(this);
+    };
+
+    $.jqplot.PyramidGridRenderer.prototype = new $.jqplot.CanvasGridRenderer();
+    $.jqplot.PyramidGridRenderer.prototype.constructor = $.jqplot.PyramidGridRenderer;
+    
+    // called with context of Grid object
+    $.jqplot.CanvasGridRenderer.prototype.init = function(options) {
+        this._ctx;
+        this.plotBands = {
+            show: false,
+            color: 'rgb(230, 219, 179)',
+            axis: 'y',
+            start: null,
+            interval: 10
+        };
+        $.extend(true, this, options);
+        // set the shadow renderer options
+        var sopts = {lineJoin:'miter', lineCap:'round', fill:false, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.shadowWidth, closePath:false, strokeStyle:this.shadowColor};
+        this.renderer.shadowRenderer.init(sopts);
+    };
+    
+    $.jqplot.PyramidGridRenderer.prototype.draw = function() {
+        this._ctx = this._elem.get(0).getContext("2d");
+        var ctx = this._ctx;
+        var axes = this._axes;
+        var xp = axes.xaxis.u2p;
+        var yp = axes.yMidAxis.u2p;
+        var xnudge = axes.xaxis.max/1000.0;
+        var xp0 = xp(0);
+        var xpn = xp(xnudge);
+        var ax = ['xaxis', 'yaxis', 'x2axis', 'y2axis','yMidAxis'];
+        // Add the grid onto the grid canvas.  This is the bottom most layer.
+        ctx.save();
+        ctx.clearRect(0, 0, this._plotDimensions.width, this._plotDimensions.height);
+        ctx.fillStyle = this.backgroundColor || this.background;
+
+        ctx.fillRect(this._left, this._top, this._width, this._height);
+
+        if (this.plotBands.show) {
+            ctx.save();
+            var pb = this.plotBands;
+            ctx.fillStyle = pb.color;
+            var axis;
+            var x, y, w, h;
+            // find axis to work with
+            if (pb.axis.charAt(0) === 'x') {
+                if (axes.xaxis.show) {
+                    axis = axes.xaxis;
+                }
+            }
+            else if (pb.axis.charAt(0) === 'y') {
+                if (axes.yaxis.show) {
+                    axis = axes.yaxis;
+                }
+                else if (axes.y2axis.show) {
+                    axis = axes.y2axis;
+                }
+                else if (axes.yMidAxis.show) {
+                    axis = axes.yMidAxis;
+                }
+            }
+
+            if (axis !== undefined) {
+                // draw some rectangles
+                var start = pb.start;
+                if (start === null) {
+                    start = axis.min;
+                }
+                for (var i = start; i < axis.max; i += 2 * pb.interval) {
+                    if (axis.name.charAt(0) === 'y') {
+                        x = this._left;
+                        if ((i + pb.interval) < axis.max) {
+                            y = axis.series_u2p(i + pb.interval) + this._top;
+                        }
+                        else {
+                            y = axis.series_u2p(axis.max) + this._top;
+                        }
+                        w = this._right - this._left;
+                        h = axis.series_u2p(start) - axis.series_u2p(start + pb.interval);
+                        ctx.fillRect(x, y, w, h);
+                    }
+                    // else {
+                    //     y = 0;
+                    //     x = axis.series_u2p(i);
+                    //     h = this._height;
+                    //     w = axis.series_u2p(start + pb.interval) - axis.series_u2p(start);
+                    // }
+
+                }
+            }
+            ctx.restore();
+        }
+        
+        ctx.save();
+        ctx.lineJoin = 'miter';
+        ctx.lineCap = 'butt';
+        ctx.lineWidth = this.gridLineWidth;
+        ctx.strokeStyle = this.gridLineColor;
+        var b, e, s, m;
+        for (var i=5; i>0; i--) {
+            var name = ax[i-1];
+            var axis = axes[name];
+            var ticks = axis._ticks;
+            var numticks = ticks.length;
+            if (axis.show) {
+                if (axis.drawBaseline) {
+                    var bopts = {};
+                    if (axis.baselineWidth !== null) {
+                        bopts.lineWidth = axis.baselineWidth;
+                    }
+                    if (axis.baselineColor !== null) {
+                        bopts.strokeStyle = axis.baselineColor;
+                    }
+                    switch (name) {
+                        case 'xaxis':
+                            if (axes.yMidAxis.show) {
+                                drawLine (this._left, this._bottom, xp0, this._bottom, bopts);
+                                drawLine (xpn, this._bottom, this._right, this._bottom, bopts);
+                            }
+                            else {
+                                drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
+                            }
+                            break;
+                        case 'yaxis':
+                            drawLine (this._left, this._bottom, this._left, this._top, bopts);
+                            break;
+                        case 'yMidAxis':               
+                            drawLine(xp0, this._bottom, xp0, this._top, bopts);
+                            drawLine(xpn, this._bottom, xpn, this._top, bopts);
+                            break;
+                        case 'x2axis':
+                            if (axes.yMidAxis.show) {
+                                drawLine (this._left, this._top, xp0, this._top, bopts);
+                                drawLine (xpn, this._top, this._right, this._top, bopts);
+                            }
+                            else {
+                                drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
+                            }
+                            break;
+                        case 'y2axis':
+                            drawLine (this._right, this._bottom, this._right, this._top, bopts);
+                            break;
+
+                    }
+                }
+                for (var j=numticks; j>0; j--) {
+                    var t = ticks[j-1];
+                    if (t.show) {
+                        var pos = Math.round(axis.u2p(t.value)) + 0.5;
+                        switch (name) {
+                            case 'xaxis':
+                                // draw the grid line if we should
+                                if (t.showGridline && this.drawGridlines && (!t.isMinorTick || axis.showMinorTicks)) {
+                                    drawLine(pos, this._top, pos, this._bottom);
+                                }
+                                
+                                // draw the mark
+                                if (t.showMark && t.mark && (!t.isMinorTick || axis.showMinorTicks)) {
+                                    s = t.markSize;
+                                    m = t.mark;
+                                    var pos = Math.round(axis.u2p(t.value)) + 0.5;
+                                    switch (m) {
+                                        case 'outside':
+                                            b = this._bottom;
+                                            e = this._bottom+s;
+                                            break;
+                                        case 'inside':
+                                            b = this._bottom-s;
+                                            e = this._bottom;
+                                            break;
+                                        case 'cross':
+                                            b = this._bottom-s;
+                                            e = this._bottom+s;
+                                            break;
+                                        default:
+                                            b = this._bottom;
+                                            e = this._bottom+s;
+                                            break;
+                                    }
+                                    // draw the shadow
+                                    if (this.shadow) {
+                                        this.renderer.shadowRenderer.draw(ctx, [[pos,b],[pos,e]], {lineCap:'butt', lineWidth:this.gridLineWidth, offset:this.gridLineWidth*0.75, depth:2, fill:false, closePath:false});
+                                    }
+                                    // draw the line
+                                    drawLine(pos, b, pos, e);
+                                }
+                                break;
+                            case 'yaxis':
+                                // draw the grid line
+                                if (t.showGridline && this.drawGridlines && (!t.isMinorTick || axis.showMinorTicks)) {
+                                    drawLine(this._right, pos, this._left, pos);
+                                }
+
+                                // draw the mark
+                                if (t.showMark && t.mark && (!t.isMinorTick || axis.showMinorTicks)) {
+                                    s = t.markSize;
+                                    m = t.mark;
+                                    var pos = Math.round(axis.u2p(t.value)) + 0.5;
+                                    switch (m) {
+                                        case 'outside':
+                                            b = this._left-s;
+                                            e = this._left;
+                                            break;
+                                        case 'inside':
+                                            b = this._left;
+                                            e = this._left+s;
+                                            break;
+                                        case 'cross':
+                                            b = this._left-s;
+                                            e = this._left+s;
+                                            break;
+                                        default:
+                                            b = this._left-s;
+                                            e = this._left;
+                                            break;
+                                            }
+                                    // draw the shadow
+                                    if (this.shadow) {
+                                        this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
+                                    }
+                                    drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
+                                }
+                                break;
+                            case 'yMidAxis':
+                                // draw the grid line
+                                if (t.showGridline && this.drawGridlines && (!t.isMinorTick || axis.showMinorTicks)) {
+                                    drawLine(this._left, pos, xp0, pos);
+                                    drawLine(xpn, pos, this._right, pos);
+                                }
+                                // draw the mark
+                                if (t.showMark && t.mark && (!t.isMinorTick || axis.showMinorTicks)) {
+                                    s = t.markSize;
+                                    m = t.mark;
+                                    var pos = Math.round(axis.u2p(t.value)) + 0.5;
+
+                                    b = xp0;
+                                    e = xp0 + s;
+                                    // draw the shadow
+                                    if (this.shadow) {
+                                        this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
+                                    }
+                                    drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
+
+                                    b = xpn - s;
+                                    e = xpn;
+                                    // draw the shadow
+                                    if (this.shadow) {
+                                        this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
+                                    }
+                                    drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
+                                }
+                                break;
+                            case 'x2axis':
+                                // draw the grid line
+                                if (t.showGridline && this.drawGridlines && (!t.isMinorTick || axis.showMinorTicks)) {
+                                    drawLine(pos, this._bottom, pos, this._top);
+                                }
+
+                                // draw the mark
+                                if (t.showMark && t.mark && (!t.isMinorTick || axis.showMinorTicks)) {
+                                    s = t.markSize;
+                                    m = t.mark;
+                                    var pos = Math.round(axis.u2p(t.value)) + 0.5;
+                                    switch (m) {
+                                        case 'outside':
+                                            b = this._top-s;
+                                            e = this._top;
+                                            break;
+                                        case 'inside':
+                                            b = this._top;
+                                            e = this._top+s;
+                                            break;
+                                        case 'cross':
+                                            b = this._top-s;
+                                            e = this._top+s;
+                                            break;
+                                        default:
+                                            b = this._top-s;
+                                            e = this._top;
+                                            break;
+                                            }
+                                    // draw the shadow
+                                    if (this.shadow) {
+                                        this.renderer.shadowRenderer.draw(ctx, [[pos,b],[pos,e]], {lineCap:'butt', lineWidth:this.gridLineWidth, offset:this.gridLineWidth*0.75, depth:2, fill:false, closePath:false});
+                                    }
+                                    drawLine(pos, b, pos, e);
+                                }
+                                break;
+                            case 'y2axis':
+                                // draw the grid line
+                                if (t.showGridline && this.drawGridlines && (!t.isMinorTick || axis.showMinorTicks)) {
+                                    drawLine(this._left, pos, this._right, pos);
+                                }
+
+                                // draw the mark
+                                if (t.showMark && t.mark && (!t.isMinorTick || axis.showMinorTicks)) {
+                                    s = t.markSize;
+                                    m = t.mark;
+                                    var pos = Math.round(axis.u2p(t.value)) + 0.5;
+                                    switch (m) {
+                                        case 'outside':
+                                            b = this._right;
+                                            e = this._right+s;
+                                            break;
+                                        case 'inside':
+                                            b = this._right-s;
+                                            e = this._right;
+                                            break;
+                                        case 'cross':
+                                            b = this._right-s;
+                                            e = this._right+s;
+                                            break;
+                                        default:
+                                            b = this._right;
+                                            e = this._right+s;
+                                            break;
+                                            }
+                                    // draw the shadow
+                                    if (this.shadow) {
+                                        this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
+                                    }
+                                    drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
+                                }
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                }
+                t = null;
+            }
+            axis = null;
+            ticks = null;
+        }
+        
+        ctx.restore();
+        
+        function drawLine(bx, by, ex, ey, opts) {
+            ctx.save();
+            opts = opts || {};
+            if (opts.lineWidth == null || opts.lineWidth != 0){
+                $.extend(true, ctx, opts);
+                ctx.beginPath();
+                ctx.moveTo(bx, by);
+                ctx.lineTo(ex, ey);
+                ctx.stroke();
+            }
+            ctx.restore();
+        }
+        
+        if (this.shadow) {
+            if (axes.yMidAxis.show) {
+                var points = [[this._left, this._bottom], [xp0, this._bottom]];
+                this.renderer.shadowRenderer.draw(ctx, points);
+                var points = [[xpn, this._bottom], [this._right, this._bottom], [this._right, this._top]];
+                this.renderer.shadowRenderer.draw(ctx, points);
+                var points = [[xp0, this._bottom], [xp0, this._top]];
+                this.renderer.shadowRenderer.draw(ctx, points);
+            }
+            else {
+                var points = [[this._left, this._bottom], [this._right, this._bottom], [this._right, this._top]];
+                this.renderer.shadowRenderer.draw(ctx, points);
+            }
+        }
+        // Now draw border around grid.  Use axis border definitions. start at
+        // upper left and go clockwise.
+        if (this.borderWidth != 0 && this.drawBorder) {
+            if (axes.yMidAxis.show) {
+                drawLine (this._left, this._top, xp0, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth});
+                drawLine (xpn, this._top, this._right, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth});
+                drawLine (this._right, this._top, this._right, this._bottom, {lineCap:'round', strokeStyle:axes.y2axis.borderColor, lineWidth:axes.y2axis.borderWidth});
+                drawLine (this._right, this._bottom, xpn, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth});
+                drawLine (xp0, this._bottom, this._left, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth});
+                drawLine (this._left, this._bottom, this._left, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
+                drawLine (xp0, this._bottom, xp0, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
+                drawLine (xpn, this._bottom, xpn, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
+            }
+            else {
+                drawLine (this._left, this._top, this._right, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth});
+                drawLine (this._right, this._top, this._right, this._bottom, {lineCap:'round', strokeStyle:axes.y2axis.borderColor, lineWidth:axes.y2axis.borderWidth});
+                drawLine (this._right, this._bottom, this._left, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth});
+                drawLine (this._left, this._bottom, this._left, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
+            }
+        }
+        // ctx.lineWidth = this.borderWidth;
+        // ctx.strokeStyle = this.borderColor;
+        // ctx.strokeRect(this._left, this._top, this._width, this._height);
+        
+        ctx.restore();
+        ctx =  null;
+        axes = null;
+    };
+})(jQuery); 
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pyramidRenderer.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pyramidRenderer.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.pyramidRenderer.js	(revision 2119)
@@ -0,0 +1,514 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+
+    // Need to ensure pyramid axis and grid renderers are loaded.
+    // You should load these with script tags in the html head, that is more efficient
+    // as the browser will cache the request.
+    // Note, have to block with synchronous request in order to execute bar renderer code.
+    if ($.jqplot.PyramidAxisRenderer === undefined) {
+        $.ajax({
+            url: $.jqplot.pluginLocation + 'jqplot.pyramidAxisRenderer.js',
+            dataType: "script",
+            async: false
+        });
+    }
+    
+    if ($.jqplot.PyramidGridRenderer === undefined) {
+        $.ajax({
+            url: $.jqplot.pluginLocation + 'jqplot.pyramidGridRenderer.js',
+            dataType: "script",
+            async: false
+        });
+    }
+
+    $.jqplot.PyramidRenderer = function(){
+        $.jqplot.LineRenderer.call(this);
+    };
+    
+    $.jqplot.PyramidRenderer.prototype = new $.jqplot.LineRenderer();
+    $.jqplot.PyramidRenderer.prototype.constructor = $.jqplot.PyramidRenderer;
+    
+    // called with scope of a series
+    $.jqplot.PyramidRenderer.prototype.init = function(options, plot) {
+        options = options || {};
+        this._type = 'pyramid';
+        // Group: Properties
+        //
+        // prop: barPadding
+        this.barPadding = 10;
+        this.barWidth = null;
+        // prop: fill
+        // True to fill the bars.
+        this.fill = true;
+        // prop: highlightMouseOver
+        // True to highlight slice when moused over.
+        // This must be false to enable highlightMouseDown to highlight when clicking on a slice.
+        this.highlightMouseOver = true;
+        // prop: highlightMouseDown
+        // True to highlight when a mouse button is pressed over a slice.
+        // This will be disabled if highlightMouseOver is true.
+        this.highlightMouseDown = false;
+        // prop: highlightColors
+        // an array of colors to use when highlighting a slice.
+        this.highlightColors = [];
+        // prop highlightThreshold
+        // Expand the highlightable region in the x direction.
+        // E.g. a value of 3 will highlight a bar when the mouse is
+        // within 3 pixels of the bar in the x direction.
+        this.highlightThreshold = 2;
+        // prop: synchronizeHighlight
+        // Index of another series to highlight when this series is highlighted.
+        // null or false to not synchronize.
+        this.synchronizeHighlight = false;
+        // prop: offsetBars
+        // False will center bars on their y value.
+        // True will push bars up by 1/2 bar width to fill between their y values.
+        // If true, there needs to be 1 more tick than there are bars.
+        this.offsetBars = false;
+        
+        // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
+        if (options.highlightMouseDown && options.highlightMouseOver == null) {
+            options.highlightMouseOver = false;
+        }
+
+        this.side = 'right';
+        
+        $.extend(true, this, options);
+
+        // if (this.fill === false) {
+        //     this.shadow = false;
+        // }
+
+        if (this.side === 'left') {
+            this._highlightThreshold = [[-this.highlightThreshold, 0], [-this.highlightThreshold, 0], [0,0], [0,0]];
+        }
+
+        else {
+            this._highlightThreshold = [[0,0], [0,0], [this.highlightThreshold, 0], [this.highlightThreshold, 0]];
+        }
+        
+        this.renderer.options = options;
+        // index of the currenty highlighted point, if any
+        this._highlightedPoint = null;
+        // Array of actual data colors used for each data point.
+        this._dataColors = [];
+        this._barPoints = [];
+        this.fillAxis = 'y';
+        this._primaryAxis = '_yaxis';
+        this._xnudge = 0;
+        
+        // set the shape renderer options
+        var opts = {lineJoin:'miter', lineCap:'butt', fill:this.fill, fillRect:this.fill, isarc:false, strokeStyle:this.color, fillStyle:this.color, closePath:this.fill, lineWidth: this.lineWidth};
+        this.renderer.shapeRenderer.init(opts);
+        // set the shadow renderer options
+        var shadow_offset = options.shadowOffset;
+        // set the shadow renderer options
+        if (shadow_offset == null) {
+            // scale the shadowOffset to the width of the line.
+            if (this.lineWidth > 2.5) {
+                shadow_offset = 1.25 * (1 + (Math.atan((this.lineWidth/2.5))/0.785398163 - 1)*0.6);
+                // var shadow_offset = this.shadowOffset;
+            }
+            // for skinny lines, don't make such a big shadow.
+            else {
+                shadow_offset = 1.25 * Math.atan((this.lineWidth/2.5))/0.785398163;
+            }
+        }
+        var sopts = {lineJoin:'miter', lineCap:'butt', fill:this.fill, fillRect:this.fill, isarc:false, angle:this.shadowAngle, offset:shadow_offset, alpha:this.shadowAlpha, depth:this.shadowDepth, closePath:this.fill, lineWidth: this.lineWidth};
+        this.renderer.shadowRenderer.init(sopts);
+
+        plot.postDrawHooks.addOnce(postPlotDraw);
+        plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
+
+        // if this is the left side of pyramid, set y values to negative.
+        if (this.side === 'left') {
+            for (var i=0, l=this.data.length; i<l; i++) {
+                this.data[i][1] = -Math.abs(this.data[i][1]);
+            }
+        }
+    };
+    
+    // setGridData
+    // converts the user data values to grid coordinates and stores them
+    // in the gridData array.
+    // Called with scope of a series.
+    $.jqplot.PyramidRenderer.prototype.setGridData = function(plot) {
+        // recalculate the grid data
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        var data = this._plotData;
+        var pdata = this._prevPlotData;
+        this.gridData = [];
+        this._prevGridData = [];
+        var l = data.length;
+        var adjust = false;
+        var i;
+
+        // if any data values are < 0,  consider this a negative series
+        for (i = 0; i < l; i++) {
+            if (data[i][1] < 0) {
+                this.side = 'left';
+            }
+        }
+
+        if (this._yaxis.name === 'yMidAxis' && this.side === 'right') {
+            this._xnudge = this._xaxis.max/2000.0;
+            adjust = true;
+        }
+
+        for (i = 0; i < l; i++) {
+            // if not a line series or if no nulls in data, push the converted point onto the array.
+            if (data[i][0] != null && data[i][1] != null) {
+                this.gridData.push([xp(data[i][1]), yp(data[i][0])]);
+            }
+            // else if there is a null, preserve it.
+            else if (data[i][0] == null) {
+                this.gridData.push([xp(data[i][1]), null]);
+            }
+            else if (data[i][1] == null) {
+                this.gridData.push(null, [yp(data[i][0])]);
+            }
+            // finally, adjust x grid data if have to
+            if (data[i][1] === 0 && adjust) {
+                this.gridData[i][0] = xp(this._xnudge);
+            }
+        }
+    };
+    
+    // makeGridData
+    // converts any arbitrary data values to grid coordinates and
+    // returns them.  This method exists so that plugins can use a series'
+    // linerenderer to generate grid data points without overwriting the
+    // grid data associated with that series.
+    // Called with scope of a series.
+    $.jqplot.PyramidRenderer.prototype.makeGridData = function(data, plot) {
+        // recalculate the grid data
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        var gd = [];
+        var l = data.length;
+        var adjust = false;
+        var i;
+
+        // if any data values are < 0,  consider this a negative series
+        for (i = 0; i < l; i++) {
+            if (data[i][1] < 0) {
+                this.side = 'left';
+            }
+        }
+
+        if (this._yaxis.name === 'yMidAxis' && this.side === 'right') {
+            this._xnudge = this._xaxis.max/2000.0;
+            adjust = true;
+        }
+
+        for (i = 0; i < l; i++) {
+            // if not a line series or if no nulls in data, push the converted point onto the array.
+            if (data[i][0] != null && data[i][1] != null) {
+                gd.push([xp(data[i][1]), yp(data[i][0])]);
+            }
+            // else if there is a null, preserve it.
+            else if (data[i][0] == null) {
+                gd.push([xp(data[i][1]), null]);
+            }
+            else if (data[i][1] == null) {
+                gd.push([null, yp(data[i][0])]);
+            }
+            // finally, adjust x grid data if have to
+            if (data[i][1] === 0 && adjust) {
+                gd[i][0] = xp(this._xnudge);
+            }
+        }
+
+        return gd;
+    };
+
+    $.jqplot.PyramidRenderer.prototype.setBarWidth = function() {
+        // need to know how many data values we have on the approprate axis and figure it out.
+        var i;
+        var nvals = 0;
+        var nseries = 0;
+        var paxis = this[this._primaryAxis];
+        var s, series, pos;
+        nvals = paxis.max - paxis.min;
+        var nticks = paxis.numberTicks;
+        var nbins = (nticks-1)/2;
+        // so, now we have total number of axis values.
+        var temp = (this.barPadding === 0) ? 1.0 : 0;
+        if (paxis.name == 'xaxis' || paxis.name == 'x2axis') {
+            this.barWidth = (paxis._offsets.max - paxis._offsets.min) / nvals - this.barPadding + temp;
+        }
+        else {
+            if (this.fill) {
+                this.barWidth = (paxis._offsets.min - paxis._offsets.max) / nvals - this.barPadding + temp;
+            }
+            else {
+                this.barWidth = (paxis._offsets.min - paxis._offsets.max) / nvals;
+            }
+        }
+    };
+    
+    $.jqplot.PyramidRenderer.prototype.draw = function(ctx, gridData, options) {
+        var i;
+        // Ughhh, have to make a copy of options b/c it may be modified later.
+        var opts = $.extend({}, options);
+        var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
+        var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
+        var fill = (opts.fill != undefined) ? opts.fill : this.fill;
+        var xp = this._xaxis.series_u2p;
+        var yp = this._yaxis.series_u2p;
+        var pointx, pointy;
+        // clear out data colors.
+        this._dataColors = [];
+        this._barPoints = [];
+        
+        if (this.renderer.options.barWidth == null) {
+            this.renderer.setBarWidth.call(this);
+        }
+        
+        // var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
+        // var nvals = temp[0];
+        // var nseries = temp[1];
+        // var pos = temp[2];
+        var points = [],
+            w,
+            h;
+        
+        // this._barNudge = 0;
+
+        if (showLine) {
+            var negativeColors = new $.jqplot.ColorGenerator(this.negativeSeriesColors);
+            var positiveColors = new $.jqplot.ColorGenerator(this.seriesColors);
+            var negativeColor = negativeColors.get(this.index);
+            if (! this.useNegativeColors) {
+                negativeColor = opts.fillStyle;
+            }
+            var positiveColor = opts.fillStyle;
+            var base;
+            var xstart = this._xaxis.series_u2p(this._xnudge);
+            var ystart = this._yaxis.series_u2p(this._yaxis.min);
+            var yend = this._yaxis.series_u2p(this._yaxis.max);
+            var bw = this.barWidth;
+            var bw2 = bw/2.0;
+            var points = [];
+            var yadj = this.offsetBars ? bw2 : 0;
+            
+            for (var i=0, l=gridData.length; i<l; i++) {
+                if (this.data[i][0] == null) {
+                    continue;
+                }
+                base = gridData[i][1];
+                // not stacked and first series in stack
+
+                if (this._plotData[i][1] < 0) {
+                    if (this.varyBarColor && !this._stack) {
+                        if (this.useNegativeColors) {
+                            opts.fillStyle = negativeColors.next();
+                        }
+                        else {
+                            opts.fillStyle = positiveColors.next();
+                        }
+                    }
+                }
+                else {
+                    if (this.varyBarColor && !this._stack) {
+                        opts.fillStyle = positiveColors.next();
+                    }
+                    else {
+                        opts.fillStyle = positiveColor;
+                    }                    
+                }
+                
+                if (this.fill) {
+
+                    if (this._plotData[i][1] >= 0) {
+                        // xstart = this._xaxis.series_u2p(this._xnudge);
+                        w = gridData[i][0] - xstart;
+                        h = this.barWidth;
+                        points = [xstart, base - bw2 - yadj, w, h];
+                    }
+                    else {
+                        // xstart = this._xaxis.series_u2p(0);
+                        w = xstart - gridData[i][0];
+                        h = this.barWidth;
+                        points = [gridData[i][0], base - bw2 - yadj, w, h];
+                    }
+
+                    this._barPoints.push([[points[0], points[1] + h], [points[0], points[1]], [points[0] + w, points[1]], [points[0] + w, points[1] + h]]);
+
+                    if (shadow) {
+                        this.renderer.shadowRenderer.draw(ctx, points);
+                    }
+                    var clr = opts.fillStyle || this.color;
+                    this._dataColors.push(clr);
+                    this.renderer.shapeRenderer.draw(ctx, points, opts); 
+                }
+
+                else {
+                    if (i === 0) {
+                        points =[[xstart, ystart], [gridData[i][0], ystart], [gridData[i][0], gridData[i][1] - bw2 - yadj]];
+                    }
+
+                    else if (i < l-1) {
+                        points = points.concat([[gridData[i-1][0], gridData[i-1][1] - bw2 - yadj], [gridData[i][0], gridData[i][1] + bw2 - yadj], [gridData[i][0], gridData[i][1] - bw2 - yadj]]);
+                    } 
+
+                    // finally, draw the line
+                    else {
+                        points = points.concat([[gridData[i-1][0], gridData[i-1][1] - bw2 - yadj], [gridData[i][0], gridData[i][1] + bw2 - yadj], [gridData[i][0], yend], [xstart, yend]]);
+                    
+                        if (shadow) {
+                            this.renderer.shadowRenderer.draw(ctx, points);
+                        }
+                        var clr = opts.fillStyle || this.color;
+                        this._dataColors.push(clr);
+                        this.renderer.shapeRenderer.draw(ctx, points, opts);
+                    }
+                }
+            }  
+        }        
+        
+        if (this.highlightColors.length == 0) {
+            this.highlightColors = $.jqplot.computeHighlightColors(this._dataColors);
+        }
+        
+        else if (typeof(this.highlightColors) == 'string') {
+            this.highlightColors = [];
+            for (var i=0; i<this._dataColors.length; i++) {
+                this.highlightColors.push(this.highlightColors);
+            }
+        }
+        
+    };
+
+        
+    // setup default renderers for axes and legend so user doesn't have to
+    // called with scope of plot
+    function preInit(target, data, options) {
+        options = options || {};
+        options.axesDefaults = options.axesDefaults || {};
+        options.grid = options.grid || {};
+        options.legend = options.legend || {};
+        options.seriesDefaults = options.seriesDefaults || {};
+        // only set these if there is a pie series
+        var setopts = false;
+        if (options.seriesDefaults.renderer === $.jqplot.PyramidRenderer) {
+            setopts = true;
+        }
+        else if (options.series) {
+            for (var i=0; i < options.series.length; i++) {
+                if (options.series[i].renderer === $.jqplot.PyramidRenderer) {
+                    setopts = true;
+                }
+            }
+        }
+        
+        if (setopts) {
+            options.axesDefaults.renderer = $.jqplot.PyramidAxisRenderer;
+            options.grid.renderer = $.jqplot.PyramidGridRenderer;
+            options.seriesDefaults.pointLabels = {show: false};
+        }
+    }
+    
+    // called within context of plot
+    // create a canvas which we can draw on.
+    // insert it before the eventCanvas, so eventCanvas will still capture events.
+    function postPlotDraw() {
+        // Memory Leaks patch    
+        if (this.plugins.pyramidRenderer && this.plugins.pyramidRenderer.highlightCanvas) {
+
+            this.plugins.pyramidRenderer.highlightCanvas.resetCanvas();
+            this.plugins.pyramidRenderer.highlightCanvas = null;
+        }
+         
+        this.plugins.pyramidRenderer = {highlightedSeriesIndex:null};
+        this.plugins.pyramidRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
+        
+        this.eventCanvas._elem.before(this.plugins.pyramidRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-pyramidRenderer-highlight-canvas', this._plotDimensions, this));
+        this.plugins.pyramidRenderer.highlightCanvas.setContext();
+        this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
+    }  
+    
+    function highlight (plot, sidx, pidx, points) {
+        var s = plot.series[sidx];
+        var canvas = plot.plugins.pyramidRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        s._highlightedPoint = pidx;
+        plot.plugins.pyramidRenderer.highlightedSeriesIndex = sidx;
+        var opts = {fillStyle: s.highlightColors[pidx], fillRect: false};
+        s.renderer.shapeRenderer.draw(canvas._ctx, points, opts);
+        if (s.synchronizeHighlight !== false && plot.series.length >= s.synchronizeHighlight && s.synchronizeHighlight !== sidx) {
+            s = plot.series[s.synchronizeHighlight];
+            opts = {fillStyle: s.highlightColors[pidx], fillRect: false};
+            s.renderer.shapeRenderer.draw(canvas._ctx, s._barPoints[pidx], opts);
+        }
+        canvas = null;
+    }
+    
+    function unhighlight (plot) {
+        var canvas = plot.plugins.pyramidRenderer.highlightCanvas;
+        canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
+        for (var i=0; i<plot.series.length; i++) {
+            plot.series[i]._highlightedPoint = null;
+        }
+        plot.plugins.pyramidRenderer.highlightedSeriesIndex = null;
+        plot.target.trigger('jqplotDataUnhighlight');
+        canvas =  null;
+    }
+    
+    
+    function handleMove(ev, gridpos, datapos, neighbor, plot) {
+        if (neighbor) {
+            var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
+            var evt1 = jQuery.Event('jqplotDataMouseOver');
+            evt1.pageX = ev.pageX;
+            evt1.pageY = ev.pageY;
+            plot.target.trigger(evt1, ins);
+            if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.pyramidRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
+                var evt = jQuery.Event('jqplotDataHighlight');
+                evt.which = ev.which;
+                evt.pageX = ev.pageX;
+                evt.pageY = ev.pageY;
+                plot.target.trigger(evt, ins);
+                highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
+            }
+        }
+        else if (neighbor == null) {
+            unhighlight (plot);
+        }
+    }
+
+    // Have to add hook here, becuase it needs called before series is inited.
+    $.jqplot.preInitHooks.push(preInit);
+    
+
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.trendline.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.trendline.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jqplot/plugins/jqplot.trendline.js	(revision 2119)
@@ -0,0 +1,223 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.8
+ * Revision: 1250
+ *
+ * Copyright (c) 2009-2013 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ * 
+ */
+(function($) {
+    
+    /**
+     * Class: $.jqplot.Trendline
+     * Plugin which will automatically compute and draw trendlines for plotted data.
+     */
+    $.jqplot.Trendline = function() {
+        // Group: Properties
+        
+        // prop: show
+        // Wether or not to show the trend line.
+        this.show = $.jqplot.config.enablePlugins;
+        // prop: color
+        // CSS color spec for the trend line.
+        // By default this wil be the same color as the primary line.
+        this.color = '#666666';
+        // prop: renderer
+        // Renderer to use to draw the trend line.
+        // The data series that is plotted may not be rendered as a line.
+        // Therefore, we use our own line renderer here to draw a trend line.
+        this.renderer = new $.jqplot.LineRenderer();
+        // prop: rendererOptions
+        // Options to pass to the line renderer.
+        // By default, markers are not shown on trend lines.
+        this.rendererOptions = {marker:{show:false}};
+        // prop: label
+        // Label for the trend line to use in the legend.
+        this.label = '';
+        // prop: type
+        // Either 'exponential', 'exp', or 'linear'.
+        this.type = 'linear';
+        // prop: shadow
+        // true or false, whether or not to show the shadow.
+        this.shadow = true;
+        // prop: markerRenderer
+        // Renderer to use to draw markers on the line.
+        // I think this is wrong.
+        this.markerRenderer = {show:false};
+        // prop: lineWidth
+        // Width of the trend line.
+        this.lineWidth = 1.5;
+        // prop: shadowAngle
+        // Angle of the shadow on the trend line.
+        this.shadowAngle = 45;
+        // prop: shadowOffset
+        // pixel offset for each stroke of the shadow.
+        this.shadowOffset = 1.0;
+        // prop: shadowAlpha
+        // Alpha transparency of the shadow.
+        this.shadowAlpha = 0.07;
+        // prop: shadowDepth
+        // number of strokes to make of the shadow.
+        this.shadowDepth = 3;
+        this.isTrendline = true;
+        
+    };
+    
+    $.jqplot.postSeriesInitHooks.push(parseTrendLineOptions);
+    $.jqplot.postDrawSeriesHooks.push(drawTrendline);
+    $.jqplot.addLegendRowHooks.push(addTrendlineLegend);
+    
+    // called witin scope of the legend object
+    // current series passed in
+    // must return null or an object {label:label, color:color}
+    function addTrendlineLegend(series) {
+        var ret = null;
+        if (series.trendline && series.trendline.show) {
+            var lt = series.trendline.label.toString();
+            if (lt) {
+                ret = {label:lt, color:series.trendline.color};
+            }
+        }
+        return ret;
+    }
+
+    // called within scope of a series
+    function parseTrendLineOptions (target, data, seriesDefaults, options, plot) {
+        if (this._type && (this._type === 'line' || this._type == 'bar')) {
+            this.trendline = new $.jqplot.Trendline();
+            options = options || {};
+            $.extend(true, this.trendline, {color:this.color}, seriesDefaults.trendline, options.trendline);
+            this.trendline.renderer.init.call(this.trendline, null);
+        }
+    }
+    
+    // called within scope of series object
+    function drawTrendline(sctx, options) {
+        // if we have options, merge trendline options in with precedence
+        options = $.extend(true, {}, this.trendline, options);
+
+        if (this.trendline && options.show) {
+            var fit;
+            // this.renderer.setGridData.call(this);
+            var data = options.data || this.data;
+            fit = fitData(data, this.trendline.type);
+            var gridData = options.gridData || this.renderer.makeGridData.call(this, fit.data);
+            this.trendline.renderer.draw.call(this.trendline, sctx, gridData, {showLine:true, shadow:this.trendline.shadow});
+        }
+    }
+    
+    function regression(x, y, typ)  {
+        var type = (typ == null) ? 'linear' : typ;
+        var N = x.length;
+        var slope;
+        var intercept;  
+        var SX = 0;
+        var SY = 0;
+        var SXX = 0;
+        var SXY = 0;
+        var SYY = 0;
+        var Y = [];
+        var X = [];
+    
+        if (type == 'linear') {
+            X = x;
+            Y = y;
+        }
+        else if (type == 'exp' || type == 'exponential') {
+            for ( var i=0; i<y.length; i++) {
+                // ignore points <= 0, log undefined.
+                if (y[i] <= 0) {
+                    N--;
+                }
+                else {
+                    X.push(x[i]);
+                    Y.push(Math.log(y[i]));
+                }
+            }
+        }
+
+        for ( var i = 0; i < N; i++) {
+            SX = SX + X[i];
+            SY = SY + Y[i];
+            SXY = SXY + X[i]* Y[i];
+            SXX = SXX + X[i]* X[i];
+            SYY = SYY + Y[i]* Y[i];
+        }
+
+        slope = (N*SXY - SX*SY)/(N*SXX - SX*SX);
+        intercept = (SY - slope*SX)/N;
+
+        return [slope, intercept];
+    }
+
+    function linearRegression(X,Y) {
+        var ret;
+        ret = regression(X,Y,'linear');
+        return [ret[0],ret[1]];
+    }
+
+    function expRegression(X,Y) {
+        var ret;
+        var x = X;
+        var y = Y;
+        ret = regression(x, y,'exp');
+        var base = Math.exp(ret[0]);
+        var coeff = Math.exp(ret[1]);
+        return [base, coeff];
+    }
+
+    function fitData(data, typ) {
+        var type = (typ == null) ?  'linear' : typ;
+        var ret;
+        var res;
+        var x = [];
+        var y = [];
+        var ypred = [];
+        
+        for (i=0; i<data.length; i++){
+            if (data[i] != null && data[i][0] != null && data[i][1] != null) {
+                x.push(data[i][0]);
+                y.push(data[i][1]);
+            }
+        }
+        
+        if (type == 'linear') {
+            ret = linearRegression(x,y);
+            for ( var i=0; i<x.length; i++){
+                res = ret[0]*x[i] + ret[1];
+                ypred.push([x[i], res]);
+            }
+        }
+        else if (type == 'exp' || type == 'exponential') {
+            ret = expRegression(x,y);
+            for ( var i=0; i<x.length; i++){
+                res = ret[1]*Math.pow(ret[0],x[i]);
+                ypred.push([x[i], res]);
+            }
+        }
+        return {data: ypred, slope: ret[0], intercept: ret[1]};
+    } 
+
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/external/jquery/jquery.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/external/jquery/jquery.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/external/jquery/jquery.js	(revision 2119)
@@ -0,0 +1,9789 @@
+/*!
+ * jQuery JavaScript Library v1.10.2
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2013-07-03T13:48Z
+ */
+(function( window, undefined ) {
+
+// Can't do this because several apps including ASP.NET trace
+// the stack via arguments.caller.callee and Firefox dies if
+// you try to trace through "use strict" call chains. (#13335)
+// Support: Firefox 18+
+//"use strict";
+var
+	// The deferred used on DOM ready
+	readyList,
+
+	// A central reference to the root jQuery(document)
+	rootjQuery,
+
+	// Support: IE<10
+	// For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`
+	core_strundefined = typeof undefined,
+
+	// Use the correct document accordingly with window argument (sandbox)
+	location = window.location,
+	document = window.document,
+	docElem = document.documentElement,
+
+	// Map over jQuery in case of overwrite
+	_jQuery = window.jQuery,
+
+	// Map over the $ in case of overwrite
+	_$ = window.$,
+
+	// [[Class]] -> type pairs
+	class2type = {},
+
+	// List of deleted data cache ids, so we can reuse them
+	core_deletedIds = [],
+
+	core_version = "1.10.2",
+
+	// Save a reference to some core methods
+	core_concat = core_deletedIds.concat,
+	core_push = core_deletedIds.push,
+	core_slice = core_deletedIds.slice,
+	core_indexOf = core_deletedIds.indexOf,
+	core_toString = class2type.toString,
+	core_hasOwn = class2type.hasOwnProperty,
+	core_trim = core_version.trim,
+
+	// Define a local copy of jQuery
+	jQuery = function( selector, context ) {
+		// The jQuery object is actually just the init constructor 'enhanced'
+		return new jQuery.fn.init( selector, context, rootjQuery );
+	},
+
+	// Used for matching numbers
+	core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,
+
+	// Used for splitting on whitespace
+	core_rnotwhite = /\S+/g,
+
+	// Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
+	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+	// A simple way to check for HTML strings
+	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+	// Strict HTML recognition (#11290: must start with <)
+	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
+
+	// Match a standalone tag
+	rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+	// JSON RegExp
+	rvalidchars = /^[\],:{}\s]*$/,
+	rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+	rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
+	rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/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();
+	},
+
+	// The ready event handler
+	completed = function( event ) {
+
+		// readyState === "complete" is good enough for us to call the dom ready in oldIE
+		if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) {
+			detach();
+			jQuery.ready();
+		}
+	},
+	// Clean-up method for dom ready events
+	detach = function() {
+		if ( document.addEventListener ) {
+			document.removeEventListener( "DOMContentLoaded", completed, false );
+			window.removeEventListener( "load", completed, false );
+
+		} else {
+			document.detachEvent( "onreadystatechange", completed );
+			window.detachEvent( "onload", completed );
+		}
+	};
+
+jQuery.fn = jQuery.prototype = {
+	// The current version of jQuery being used
+	jquery: core_version,
+
+	constructor: jQuery,
+	init: function( selector, context, rootjQuery ) {
+		var match, elem;
+
+		// HANDLE: $(""), $(null), $(undefined), $(false)
+		if ( !selector ) {
+			return this;
+		}
+
+		// 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
+					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 || rootjQuery ).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 rootjQuery.ready( selector );
+		}
+
+		if ( selector.selector !== undefined ) {
+			this.selector = selector.selector;
+			this.context = selector.context;
+		}
+
+		return jQuery.makeArray( selector, this );
+	},
+
+	// Start with an empty selector
+	selector: "",
+
+	// The default length of a jQuery object is 0
+	length: 0,
+
+	toArray: function() {
+		return core_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 a 'clean' array
+			this.toArray() :
+
+			// Return just the object
+			( num < 0 ? this[ this.length + num ] : this[ num ] );
+	},
+
+	// 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.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	ready: function( fn ) {
+		// Add the callback
+		jQuery.ready.promise().done( fn );
+
+		return this;
+	},
+
+	slice: function() {
+		return this.pushStack( core_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] ] : [] );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function( elem, i ) {
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	end: function() {
+		return this.prevObject || this.constructor(null);
+	},
+
+	// For internal use only.
+	// Behaves like an Array's method, not like a jQuery method.
+	push: core_push,
+	sort: [].sort,
+	splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+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;
+		target = arguments[1] || {};
+		// skip the boolean and the target
+		i = 2;
+	}
+
+	// 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 ( length === i ) {
+		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
+	// Non-digits removed to match rinlinejQuery
+	expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
+
+	noConflict: function( deep ) {
+		if ( window.$ === jQuery ) {
+			window.$ = _$;
+		}
+
+		if ( deep && window.jQuery === jQuery ) {
+			window.jQuery = _jQuery;
+		}
+
+		return jQuery;
+	},
+
+	// 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;
+		}
+
+		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+		if ( !document.body ) {
+			return setTimeout( jQuery.ready );
+		}
+
+		// 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.trigger ) {
+			jQuery( document ).trigger("ready").off("ready");
+		}
+	},
+
+	// 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 ) {
+		return !isNaN( parseFloat(obj) ) && isFinite( obj );
+	},
+
+	type: function( obj ) {
+		if ( obj == null ) {
+			return String( obj );
+		}
+		return typeof obj === "object" || typeof obj === "function" ?
+			class2type[ core_toString.call(obj) ] || "object" :
+			typeof obj;
+	},
+
+	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 &&
+				!core_hasOwn.call(obj, "constructor") &&
+				!core_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 ( jQuery.support.ownLast ) {
+			for ( key in obj ) {
+				return core_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 || core_hasOwn.call( obj, key );
+	},
+
+	isEmptyObject: function( obj ) {
+		var name;
+		for ( name in obj ) {
+			return false;
+		}
+		return true;
+	},
+
+	error: function( msg ) {
+		throw new Error( msg );
+	},
+
+	// data: string of html
+	// context (optional): If specified, the fragment will be created in this context, defaults to document
+	// keepScripts (optional): If true, will include scripts passed in the html string
+	parseHTML: function( data, context, keepScripts ) {
+		if ( !data || typeof data !== "string" ) {
+			return null;
+		}
+		if ( typeof context === "boolean" ) {
+			keepScripts = context;
+			context = false;
+		}
+		context = context || document;
+
+		var parsed = rsingleTag.exec( data ),
+			scripts = !keepScripts && [];
+
+		// Single tag
+		if ( parsed ) {
+			return [ context.createElement( parsed[1] ) ];
+		}
+
+		parsed = jQuery.buildFragment( [ data ], context, scripts );
+		if ( scripts ) {
+			jQuery( scripts ).remove();
+		}
+		return jQuery.merge( [], parsed.childNodes );
+	},
+
+	parseJSON: function( data ) {
+		// Attempt to parse using the native JSON parser first
+		if ( window.JSON && window.JSON.parse ) {
+			return window.JSON.parse( data );
+		}
+
+		if ( data === null ) {
+			return data;
+		}
+
+		if ( typeof data === "string" ) {
+
+			// Make sure leading/trailing whitespace is removed (IE can't handle it)
+			data = jQuery.trim( data );
+
+			if ( data ) {
+				// Make sure the incoming data is actual JSON
+				// Logic borrowed from http://json.org/json2.js
+				if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+					.replace( rvalidtokens, "]" )
+					.replace( rvalidbraces, "")) ) {
+
+					return ( new Function( "return " + data ) )();
+				}
+			}
+		}
+
+		jQuery.error( "Invalid JSON: " + data );
+	},
+
+	// Cross-browser xml parsing
+	parseXML: function( data ) {
+		var xml, tmp;
+		if ( !data || typeof data !== "string" ) {
+			return null;
+		}
+		try {
+			if ( window.DOMParser ) { // Standard
+				tmp = new DOMParser();
+				xml = tmp.parseFromString( data , "text/xml" );
+			} else { // IE
+				xml = new ActiveXObject( "Microsoft.XMLDOM" );
+				xml.async = "false";
+				xml.loadXML( data );
+			}
+		} catch( e ) {
+			xml = undefined;
+		}
+		if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+			jQuery.error( "Invalid XML: " + data );
+		}
+		return xml;
+	},
+
+	noop: function() {},
+
+	// Evaluates a script in a global context
+	// 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 );
+			} )( 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();
+	},
+
+	// args is for internal usage only
+	each: function( obj, callback, args ) {
+		var value,
+			i = 0,
+			length = obj.length,
+			isArray = isArraylike( obj );
+
+		if ( args ) {
+			if ( isArray ) {
+				for ( ; i < length; i++ ) {
+					value = callback.apply( obj[ i ], args );
+
+					if ( value === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( i in obj ) {
+					value = callback.apply( obj[ i ], args );
+
+					if ( value === false ) {
+						break;
+					}
+				}
+			}
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( isArray ) {
+				for ( ; i < length; i++ ) {
+					value = callback.call( obj[ i ], i, obj[ i ] );
+
+					if ( value === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( i in obj ) {
+					value = callback.call( obj[ i ], i, obj[ i ] );
+
+					if ( value === false ) {
+						break;
+					}
+				}
+			}
+		}
+
+		return obj;
+	},
+
+	// Use native String.trim function wherever possible
+	trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
+		function( text ) {
+			return text == null ?
+				"" :
+				core_trim.call( text );
+		} :
+
+		// Otherwise use our own trimming functionality
+		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 {
+				core_push.call( ret, arr );
+			}
+		}
+
+		return ret;
+	},
+
+	inArray: function( elem, arr, i ) {
+		var len;
+
+		if ( arr ) {
+			if ( core_indexOf ) {
+				return core_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 l = second.length,
+			i = first.length,
+			j = 0;
+
+		if ( typeof l === "number" ) {
+			for ( ; j < l; j++ ) {
+				first[ i++ ] = second[ j ];
+			}
+		} else {
+			while ( second[j] !== undefined ) {
+				first[ i++ ] = second[ j++ ];
+			}
+		}
+
+		first.length = i;
+
+		return first;
+	},
+
+	grep: function( elems, callback, inv ) {
+		var retVal,
+			ret = [],
+			i = 0,
+			length = elems.length;
+		inv = !!inv;
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( ; i < length; i++ ) {
+			retVal = !!callback( elems[ i ], i );
+			if ( inv !== retVal ) {
+				ret.push( elems[ i ] );
+			}
+		}
+
+		return ret;
+	},
+
+	// arg is for internal usage only
+	map: function( elems, callback, arg ) {
+		var value,
+			i = 0,
+			length = elems.length,
+			isArray = isArraylike( elems ),
+			ret = [];
+
+		// Go through the array, translating each of the items to their
+		if ( isArray ) {
+			for ( ; i < length; i++ ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+
+		// Go through every key on the object,
+		} else {
+			for ( i in elems ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+		}
+
+		// Flatten any nested arrays
+		return core_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 = core_slice.call( arguments, 2 );
+		proxy = function() {
+			return fn.apply( context || this, args.concat( core_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;
+	},
+
+	// Multifunctional method to get and set values of a collection
+	// The value/s can optionally be executed if it's a function
+	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 ) {
+				jQuery.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;
+	},
+
+	now: function() {
+		return ( new Date() ).getTime();
+	},
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations.
+	// Note: this method belongs to the css module but it's needed here for the support module.
+	// If support gets modularized, this method should be moved back to the css module.
+	swap: function( elem, options, callback, args ) {
+		var ret, name,
+			old = {};
+
+		// Remember the old values, and insert the new ones
+		for ( name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		ret = callback.apply( elem, args || [] );
+
+		// Revert the old values
+		for ( name in options ) {
+			elem.style[ name ] = old[ name ];
+		}
+
+		return ret;
+	}
+});
+
+jQuery.ready.promise = function( obj ) {
+	if ( !readyList ) {
+
+		readyList = jQuery.Deferred();
+
+		// Catch cases where $(document).ready() is called after the browser event has already occurred.
+		// we once tried to use readyState "interactive" here, but it caused issues like the one
+		// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+		if ( document.readyState === "complete" ) {
+			// Handle it asynchronously to allow scripts the opportunity to delay ready
+			setTimeout( jQuery.ready );
+
+		// Standards-based browsers support DOMContentLoaded
+		} else if ( document.addEventListener ) {
+			// Use the handy event callback
+			document.addEventListener( "DOMContentLoaded", completed, false );
+
+			// A fallback to window.onload, that will always work
+			window.addEventListener( "load", completed, false );
+
+		// 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 setTimeout( doScrollCheck, 50 );
+						}
+
+						// detach all dom ready events
+						detach();
+
+						// and execute any waiting functions
+						jQuery.ready();
+					}
+				})();
+			}
+		}
+	}
+	return readyList.promise( obj );
+};
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+	class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+function isArraylike( obj ) {
+	var length = obj.length,
+		type = jQuery.type( obj );
+
+	if ( jQuery.isWindow( obj ) ) {
+		return false;
+	}
+
+	if ( obj.nodeType === 1 && length ) {
+		return true;
+	}
+
+	return type === "array" || type !== "function" &&
+		( length === 0 ||
+		typeof length === "number" && length > 0 && ( length - 1 ) in obj );
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+/*!
+ * Sizzle CSS Selector Engine v1.10.2
+ * http://sizzlejs.com/
+ *
+ * Copyright 2013 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2013-07-03
+ */
+(function( window, undefined ) {
+
+var i,
+	support,
+	cachedruns,
+	Expr,
+	getText,
+	isXML,
+	compile,
+	outermostContext,
+	sortInput,
+
+	// Local document vars
+	setDocument,
+	document,
+	docElem,
+	documentIsHTML,
+	rbuggyQSA,
+	rbuggyMatches,
+	matches,
+	contains,
+
+	// Instance-specific data
+	expando = "sizzle" + -(new Date()),
+	preferredDoc = window.document,
+	dirruns = 0,
+	done = 0,
+	classCache = createCache(),
+	tokenCache = createCache(),
+	compilerCache = createCache(),
+	hasDuplicate = false,
+	sortOrder = function( a, b ) {
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+		return 0;
+	},
+
+	// General-purpose constants
+	strundefined = typeof undefined,
+	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 if we can't use a native one
+	indexOf = arr.indexOf || function( elem ) {
+		var i = 0,
+			len = this.length;
+		for ( ; i < len; i++ ) {
+			if ( this[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
+
+	// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+	whitespace = "[\\x20\\t\\r\\n\\f]",
+	// http://www.w3.org/TR/css3-syntax/#characters
+	characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
+
+	// Loosely modeled on CSS identifier characters
+	// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
+	// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+	identifier = characterEncoding.replace( "w", "w#" ),
+
+	// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
+	attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
+		"*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
+
+	// Prefer arguments quoted,
+	//   then not containing pseudos/brackets,
+	//   then attribute selectors/non-parenthetical expressions,
+	//   then anything else
+	// These preferences are here to reduce the number of selectors
+	//   needing tokenize in the PSEUDO preFilter
+	pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",
+
+	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
+
+	rsibling = new RegExp( whitespace + "*[+~]" ),
+	rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*)" + whitespace + "*\\]", "g" ),
+
+	rpseudo = new RegExp( pseudos ),
+	ridentifier = new RegExp( "^" + identifier + "$" ),
+
+	matchExpr = {
+		"ID": new RegExp( "^#(" + characterEncoding + ")" ),
+		"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+		"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+		"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" )
+	},
+
+	rnative = /^[^{]+\{\s*\[native \w/,
+
+	// Easily-parseable/retrievable ID or TAG or CLASS selectors
+	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+	rinputs = /^(?:input|select|textarea|button)$/i,
+	rheader = /^h\d$/i,
+
+	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
+		// Workaround erroneous numeric interpretation of +"0x"
+		return high !== high || escapedWhitespace ?
+			escaped :
+			// BMP codepoint
+			high < 0 ?
+				String.fromCharCode( high + 0x10000 ) :
+				// Supplemental Plane codepoint (surrogate pair)
+				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+	};
+
+// 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 match, elem, m, nodeType,
+		// QSA vars
+		i, groups, old, nid, newContext, newSelector;
+
+	if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+		setDocument( context );
+	}
+
+	context = context || document;
+	results = results || [];
+
+	if ( !selector || typeof selector !== "string" ) {
+		return results;
+	}
+
+	if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
+		return [];
+	}
+
+	if ( documentIsHTML && !seed ) {
+
+		// Shortcuts
+		if ( (match = rquickExpr.exec( selector )) ) {
+			// Speed-up: Sizzle("#ID")
+			if ( (m = match[1]) ) {
+				if ( nodeType === 9 ) {
+					elem = context.getElementById( m );
+					// 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, Opera, and Webkit return items
+						// by name instead of ID
+						if ( elem.id === m ) {
+							results.push( elem );
+							return results;
+						}
+					} else {
+						return results;
+					}
+				} else {
+					// Context is not a document
+					if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+						contains( context, elem ) && elem.id === m ) {
+						results.push( elem );
+						return results;
+					}
+				}
+
+			// Speed-up: Sizzle("TAG")
+			} else if ( match[2] ) {
+				push.apply( results, context.getElementsByTagName( selector ) );
+				return results;
+
+			// Speed-up: Sizzle(".CLASS")
+			} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
+				push.apply( results, context.getElementsByClassName( m ) );
+				return results;
+			}
+		}
+
+		// QSA path
+		if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+			nid = old = expando;
+			newContext = context;
+			newSelector = nodeType === 9 && selector;
+
+			// qSA works strangely on Element-rooted queries
+			// We can work around this by specifying an extra ID on the root
+			// and working up from there (Thanks to Andrew Dupont for the technique)
+			// IE 8 doesn't work on object elements
+			if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+				groups = tokenize( selector );
+
+				if ( (old = context.getAttribute("id")) ) {
+					nid = old.replace( rescape, "\\$&" );
+				} else {
+					context.setAttribute( "id", nid );
+				}
+				nid = "[id='" + nid + "'] ";
+
+				i = groups.length;
+				while ( i-- ) {
+					groups[i] = nid + toSelector( groups[i] );
+				}
+				newContext = rsibling.test( selector ) && context.parentNode || context;
+				newSelector = groups.join(",");
+			}
+
+			if ( newSelector ) {
+				try {
+					push.apply( results,
+						newContext.querySelectorAll( newSelector )
+					);
+					return results;
+				} catch(qsaError) {
+				} finally {
+					if ( !old ) {
+						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 = attrs.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]);
+				}
+			}
+		});
+	});
+}
+
+/**
+ * Detect xml
+ * @param {Element|Object} elem An element or a document
+ */
+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;
+};
+
+// Expose support vars for convenience
+support = Sizzle.support = {};
+
+/**
+ * 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 doc = node ? node.ownerDocument || node : preferredDoc,
+		parent = doc.defaultView;
+
+	// If no document and documentElement is available, return
+	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+		return document;
+	}
+
+	// Set our document
+	document = doc;
+	docElem = doc.documentElement;
+
+	// Support tests
+	documentIsHTML = !isXML( doc );
+
+	// Support: IE>8
+	// If iframe document is assigned to "document" variable and if iframe has been reloaded,
+	// IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
+	// IE6-8 do not support the defaultView property so parent will be undefined
+	if ( parent && parent.attachEvent && parent !== parent.top ) {
+		parent.attachEvent( "onbeforeunload", function() {
+			setDocument();
+		});
+	}
+
+	/* 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( doc.createComment("") );
+		return !div.getElementsByTagName("*").length;
+	});
+
+	// Check if getElementsByClassName can be trusted
+	support.getElementsByClassName = assert(function( div ) {
+		div.innerHTML = "<div class='a'></div><div class='a i'></div>";
+
+		// Support: Safari<4
+		// Catch class over-caching
+		div.firstChild.className = "i";
+		// Support: Opera<10
+		// Catch gEBCN failure to find non-leading classes
+		return div.getElementsByClassName("i").length === 2;
+	});
+
+	// 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 !doc.getElementsByName || !doc.getElementsByName( expando ).length;
+	});
+
+	// ID find and filter
+	if ( support.getById ) {
+		Expr.find["ID"] = function( id, context ) {
+			if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
+				var m = context.getElementById( id );
+				// Check parentNode to catch when Blackberry 4.6 returns
+				// nodes that are no longer in the document #6963
+				return m && m.parentNode ? [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 !== strundefined && elem.getAttributeNode("id");
+				return node && node.value === attrId;
+			};
+		};
+	}
+
+	// Tag
+	Expr.find["TAG"] = support.getElementsByTagName ?
+		function( tag, context ) {
+			if ( typeof context.getElementsByTagName !== strundefined ) {
+				return context.getElementsByTagName( tag );
+			}
+		} :
+		function( tag, context ) {
+			var elem,
+				tmp = [],
+				i = 0,
+				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 !== strundefined && 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( doc.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
+			div.innerHTML = "<select><option selected=''></option></select>";
+
+			// Support: IE8
+			// Boolean attributes and "value" are not treated correctly
+			if ( !div.querySelectorAll("[selected]").length ) {
+				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+			}
+
+			// 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");
+			}
+		});
+
+		assert(function( div ) {
+
+			// Support: Opera 10-12/IE8
+			// ^= $= *= and empty values
+			// Should not select anything
+			// Support: Windows 8 Native Apps
+			// The type attribute is restricted during .innerHTML assignment
+			var input = doc.createElement("input");
+			input.setAttribute( "type", "hidden" );
+			div.appendChild( input ).setAttribute( "t", "" );
+
+			if ( div.querySelectorAll("[t^='']").length ) {
+				rbuggyQSA.push( "[*^$]=" + 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.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
+	---------------------------------------------------------------------- */
+
+	// Element contains another
+	// Purposefully does not implement inclusive descendent
+	// As in, an element does not contain itself
+	contains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ?
+		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 = docElem.compareDocumentPosition ?
+	function( a, b ) {
+
+		// Flag for duplicate removal
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b );
+
+		if ( compare ) {
+			// Disconnected nodes
+			if ( compare & 1 ||
+				(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
+
+				// Choose the first element that is related to our preferred document
+				if ( a === doc || contains(preferredDoc, a) ) {
+					return -1;
+				}
+				if ( b === doc || contains(preferredDoc, b) ) {
+					return 1;
+				}
+
+				// Maintain original order
+				return sortInput ?
+					( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
+					0;
+			}
+
+			return compare & 4 ? -1 : 1;
+		}
+
+		// Not directly comparable, sort on existence of method
+		return a.compareDocumentPosition ? -1 : 1;
+	} :
+	function( a, b ) {
+		var cur,
+			i = 0,
+			aup = a.parentNode,
+			bup = b.parentNode,
+			ap = [ a ],
+			bp = [ b ];
+
+		// Exit early if the nodes are identical
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+
+		// Parentless nodes are either documents or disconnected
+		} else if ( !aup || !bup ) {
+			return a === doc ? -1 :
+				b === doc ? 1 :
+				aup ? -1 :
+				bup ? 1 :
+				sortInput ?
+				( indexOf.call( sortInput, a ) - indexOf.call( 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 doc;
+};
+
+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 &&
+		( !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 ?
+		support.attributes || !documentIsHTML ?
+			elem.getAttribute( name ) :
+			(val = elem.getAttributeNode(name)) && val.specified ?
+				val.value :
+				null :
+		val;
+};
+
+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 );
+		}
+	}
+
+	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
+		for ( ; (node = elem[i]); 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 (see #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[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[5] && match[2];
+
+			if ( matchExpr["CHILD"].test( match[0] ) ) {
+				return null;
+			}
+
+			// Accept quoted arguments as-is
+			if ( match[3] && match[4] !== undefined ) {
+				match[2] = match[4];
+
+			// 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 !== strundefined && 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 + " " ).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, outerCache, node, diff, nodeIndex, start,
+						dir = simple !== forward ? "nextSibling" : "previousSibling",
+						parent = elem.parentNode,
+						name = ofType && elem.nodeName.toLowerCase(),
+						useCache = !xml && !ofType;
+
+					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
+							outerCache = parent[ expando ] || (parent[ expando ] = {});
+							cache = outerCache[ type ] || [];
+							nodeIndex = cache[0] === dirruns && cache[1];
+							diff = cache[0] === dirruns && 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 ) {
+									outerCache[ type ] = [ dirruns, nodeIndex, diff ];
+									break;
+								}
+							}
+
+						// Use previously-cached element index if available
+						} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
+							diff = cache[1];
+
+						// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
+						} else {
+							// 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 ) {
+										(node[ expando ] || (node[ expando ] = {}))[ 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.call( 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 );
+					return !results.pop();
+				};
+		}),
+
+		"has": markFunction(function( selector ) {
+			return function( elem ) {
+				return Sizzle( selector, elem ).length > 0;
+			};
+		}),
+
+		"contains": markFunction(function( text ) {
+			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 only affected by element nodes and content nodes(including text(3), cdata(4)),
+			//   not comment, processing instructions, or others
+			// Thanks to Diego Perini for the nodeName shortcut
+			//   Greater than "@" means alpha characters (specifically not starting with "#" or "?")
+			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+				if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) {
+					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;
+			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+			// use getAttribute instead to test this case
+			return elem.nodeName.toLowerCase() === "input" &&
+				elem.type === "text" &&
+				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type );
+		},
+
+		// 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();
+
+function tokenize( 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 data, cache, outerCache,
+				dirkey = dirruns + " " + doneName;
+
+			// We can't set arbitrary data on XML nodes, so they don't benefit from dir 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 ] = {});
+						if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {
+							if ( (data = cache[1]) === true || data === cachedruns ) {
+								return data === true;
+							}
+						} else {
+							cache = outerCache[ dir ] = [ dirkey ];
+							cache[1] = matcher( elem, context, xml ) || cachedruns;
+							if ( cache[1] === true ) {
+								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 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.call( 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.call( checkContext, elem ) > -1;
+		}, implicitRelative, true ),
+		matchers = [ function( elem, context, xml ) {
+			return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+				(checkContext = context).nodeType ?
+					matchContext( elem, context, xml ) :
+					matchAnyContext( elem, context, xml ) );
+		} ];
+
+	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 ) {
+	// A counter to specify which element is currently being matched
+	var matcherCachedRuns = 0,
+		bySet = setMatchers.length > 0,
+		byElement = elementMatchers.length > 0,
+		superMatcher = function( seed, context, xml, results, expandContext ) {
+			var elem, j, matcher,
+				setMatched = [],
+				matchedCount = 0,
+				i = "0",
+				unmatched = seed && [],
+				outermost = expandContext != null,
+				contextBackup = outermostContext,
+				// We must always have either seed elements or context
+				elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
+				// Use integer dirruns iff this is the outermost matcher
+				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1);
+
+			if ( outermost ) {
+				outermostContext = context !== document && context;
+				cachedruns = matcherCachedRuns;
+			}
+
+			// Add elements passing elementMatchers directly to results
+			// Keep `i` a string if there are no elements so `matchedCount` will be "00" below
+			for ( ; (elem = elems[i]) != null; i++ ) {
+				if ( byElement && elem ) {
+					j = 0;
+					while ( (matcher = elementMatchers[j++]) ) {
+						if ( matcher( elem, context, xml ) ) {
+							results.push( elem );
+							break;
+						}
+					}
+					if ( outermost ) {
+						dirruns = dirrunsUnique;
+						cachedruns = ++matcherCachedRuns;
+					}
+				}
+
+				// 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 );
+					}
+				}
+			}
+
+			// Apply set filters to unmatched elements
+			matchedCount += i;
+			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, group /* 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 ( !group ) {
+			group = tokenize( selector );
+		}
+		i = group.length;
+		while ( i-- ) {
+			cached = matcherFromTokens( group[i] );
+			if ( cached[ expando ] ) {
+				setMatchers.push( cached );
+			} else {
+				elementMatchers.push( cached );
+			}
+		}
+
+		// Cache the compiled function
+		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+	}
+	return cached;
+};
+
+function multipleContexts( selector, contexts, results ) {
+	var i = 0,
+		len = contexts.length;
+	for ( ; i < len; i++ ) {
+		Sizzle( selector, contexts[i], results );
+	}
+	return results;
+}
+
+function select( selector, context, results, seed ) {
+	var i, tokens, token, type, find,
+		match = tokenize( selector );
+
+	if ( !seed ) {
+		// Try to minimize operations if there is only one group
+		if ( match.length === 1 ) {
+
+			// Take a shortcut and set the context if the root 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;
+				}
+				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 ) && 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
+	// Provide `match` to avoid retokenization if we modified the selector above
+	compile( selector, match )(
+		seed,
+		context,
+		!documentIsHTML,
+		results,
+		rsibling.test( selector )
+	);
+	return results;
+}
+
+// One-time assignments
+
+// Sort stability
+support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
+
+// Support: Chrome<14
+// 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 = "<a href='#'></a>";
+	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 = "<input/>";
+	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 (val = elem.getAttributeNode( name )) && val.specified ?
+				val.value :
+				elem[ name ] === true ? name.toLowerCase() : null;
+		}
+	});
+}
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})( window );
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+	var object = optionsCache[ options ] = {};
+	jQuery.each( options.match( core_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" ?
+		( optionsCache[ options ] || 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,
+		// End of the loop when firing
+		firingLength,
+		// Index of currently firing callback (modified by remove if needed)
+		firingIndex,
+		// First callback to fire (used internally by add and fireWith)
+		firingStart,
+		// Actual callback list
+		list = [],
+		// Stack of fire calls for repeatable lists
+		stack = !options.once && [],
+		// Fire callbacks
+		fire = function( data ) {
+			memory = options.memory && data;
+			fired = true;
+			firingIndex = firingStart || 0;
+			firingStart = 0;
+			firingLength = list.length;
+			firing = true;
+			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+				if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+					memory = false; // To prevent further calls using add
+					break;
+				}
+			}
+			firing = false;
+			if ( list ) {
+				if ( stack ) {
+					if ( stack.length ) {
+						fire( stack.shift() );
+					}
+				} else if ( memory ) {
+					list = [];
+				} else {
+					self.disable();
+				}
+			}
+		},
+		// Actual Callbacks object
+		self = {
+			// Add a callback or a collection of callbacks to the list
+			add: function() {
+				if ( list ) {
+					// First, we save the current length
+					var start = list.length;
+					(function add( args ) {
+						jQuery.each( args, function( _, arg ) {
+							var type = jQuery.type( arg );
+							if ( type === "function" ) {
+								if ( !options.unique || !self.has( arg ) ) {
+									list.push( arg );
+								}
+							} else if ( arg && arg.length && type !== "string" ) {
+								// Inspect recursively
+								add( arg );
+							}
+						});
+					})( arguments );
+					// Do we need to add the callbacks to the
+					// current firing batch?
+					if ( firing ) {
+						firingLength = list.length;
+					// With memory, if we're not firing then
+					// we should call right away
+					} else if ( memory ) {
+						firingStart = start;
+						fire( memory );
+					}
+				}
+				return this;
+			},
+			// Remove a callback from the list
+			remove: function() {
+				if ( list ) {
+					jQuery.each( arguments, function( _, arg ) {
+						var index;
+						while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+							list.splice( index, 1 );
+							// Handle firing indexes
+							if ( firing ) {
+								if ( index <= firingLength ) {
+									firingLength--;
+								}
+								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 && list.length );
+			},
+			// Remove all callbacks from the list
+			empty: function() {
+				list = [];
+				firingLength = 0;
+				return this;
+			},
+			// Have the list do nothing anymore
+			disable: function() {
+				list = stack = memory = undefined;
+				return this;
+			},
+			// Is it disabled?
+			disabled: function() {
+				return !list;
+			},
+			// Lock the list in its current state
+			lock: function() {
+				stack = undefined;
+				if ( !memory ) {
+					self.disable();
+				}
+				return this;
+			},
+			// Is it locked?
+			locked: function() {
+				return !stack;
+			},
+			// Call all callbacks with the given context and arguments
+			fireWith: function( context, args ) {
+				if ( list && ( !fired || stack ) ) {
+					args = args || [];
+					args = [ context, args.slice ? args.slice() : args ];
+					if ( firing ) {
+						stack.push( args );
+					} else {
+						fire( args );
+					}
+				}
+				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 action = tuple[ 0 ],
+								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()
+										.done( newDefer.resolve )
+										.fail( newDefer.reject )
+										.progress( newDefer.notify );
+								} else {
+									newDefer[ action + "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 = core_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 ? core_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()
+						.done( updateFunc( i, resolveContexts, resolveValues ) )
+						.fail( deferred.reject )
+						.progress( updateFunc( i, progressContexts, progressValues ) );
+				} else {
+					--remaining;
+				}
+			}
+		}
+
+		// if we're not waiting on anything, resolve the master
+		if ( !remaining ) {
+			deferred.resolveWith( resolveContexts, resolveValues );
+		}
+
+		return deferred.promise();
+	}
+});
+jQuery.support = (function( support ) {
+
+	var all, a, input, select, fragment, opt, eventName, isSupported, i,
+		div = document.createElement("div");
+
+	// Setup
+	div.setAttribute( "className", "t" );
+	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
+
+	// Finish early in limited (non-browser) environments
+	all = div.getElementsByTagName("*") || [];
+	a = div.getElementsByTagName("a")[ 0 ];
+	if ( !a || !a.style || !all.length ) {
+		return support;
+	}
+
+	// First batch of tests
+	select = document.createElement("select");
+	opt = select.appendChild( document.createElement("option") );
+	input = div.getElementsByTagName("input")[ 0 ];
+
+	a.style.cssText = "top:1px;float:left;opacity:.5";
+
+	// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+	support.getSetAttribute = div.className !== "t";
+
+	// 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;
+
+	// Get the style information from getAttribute
+	// (IE uses .cssText instead)
+	support.style = /top/.test( a.getAttribute("style") );
+
+	// Make sure that URLs aren't manipulated
+	// (IE normalizes it by default)
+	support.hrefNormalized = a.getAttribute("href") === "/a";
+
+	// Make sure that element opacity exists
+	// (IE uses filter instead)
+	// Use a regex to work around a WebKit issue. See #5145
+	support.opacity = /^0.5/.test( a.style.opacity );
+
+	// Verify style float existence
+	// (IE uses styleFloat instead of cssFloat)
+	support.cssFloat = !!a.style.cssFloat;
+
+	// Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
+	support.checkOn = !!input.value;
+
+	// Make sure that a selected-by-default option has a working selected property.
+	// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+	support.optSelected = opt.selected;
+
+	// Tests for enctype support on a form (#6743)
+	support.enctype = !!document.createElement("form").enctype;
+
+	// 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></:nav>";
+
+	// Will be defined later
+	support.inlineBlockNeedsLayout = false;
+	support.shrinkWrapBlocks = false;
+	support.pixelPosition = false;
+	support.deleteExpando = true;
+	support.noCloneEvent = true;
+	support.reliableMarginRight = true;
+	support.boxSizingReliable = true;
+
+	// Make sure checked status is properly cloned
+	input.checked = true;
+	support.noCloneChecked = input.cloneNode( true ).checked;
+
+	// Make sure that the options inside disabled selects aren't marked as disabled
+	// (WebKit marks them as disabled)
+	select.disabled = true;
+	support.optDisabled = !opt.disabled;
+
+	// Support: IE<9
+	try {
+		delete div.test;
+	} catch( e ) {
+		support.deleteExpando = false;
+	}
+
+	// Check if we can trust getAttribute("value")
+	input = document.createElement("input");
+	input.setAttribute( "value", "" );
+	support.input = input.getAttribute( "value" ) === "";
+
+	// Check if an input maintains its value after becoming a radio
+	input.value = "t";
+	input.setAttribute( "type", "radio" );
+	support.radioValue = input.value === "t";
+
+	// #11217 - WebKit loses check when the name is after the checked attribute
+	input.setAttribute( "checked", "t" );
+	input.setAttribute( "name", "t" );
+
+	fragment = document.createDocumentFragment();
+	fragment.appendChild( input );
+
+	// Check if a disconnected checkbox will retain its checked
+	// value of true after appended to the DOM (IE6/7)
+	support.appendChecked = input.checked;
+
+	// WebKit doesn't clone checked state correctly in fragments
+	support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+	// Support: IE<9
+	// Opera does not clone events (and typeof div.attachEvent === undefined).
+	// IE9-10 clones events bound via attachEvent, but they don't trigger with .click()
+	if ( div.attachEvent ) {
+		div.attachEvent( "onclick", function() {
+			support.noCloneEvent = false;
+		});
+
+		div.cloneNode( true ).click();
+	}
+
+	// Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event)
+	// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
+	for ( i in { submit: true, change: true, focusin: true }) {
+		div.setAttribute( eventName = "on" + i, "t" );
+
+		support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false;
+	}
+
+	div.style.backgroundClip = "content-box";
+	div.cloneNode( true ).style.backgroundClip = "";
+	support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+	// Support: IE<9
+	// Iteration over object's inherited properties before its own.
+	for ( i in jQuery( support ) ) {
+		break;
+	}
+	support.ownLast = i !== "0";
+
+	// Run tests that need a body at doc ready
+	jQuery(function() {
+		var container, marginDiv, tds,
+			divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",
+			body = document.getElementsByTagName("body")[0];
+
+		if ( !body ) {
+			// Return for frameset docs that don't have a body
+			return;
+		}
+
+		container = document.createElement("div");
+		container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";
+
+		body.appendChild( container ).appendChild( div );
+
+		// Support: IE8
+		// Check if table cells still have offsetWidth/Height when they are set
+		// to display:none and there are still other visible table cells in a
+		// table row; if so, offsetWidth/Height are not reliable for use when
+		// determining if an element has been hidden directly using
+		// display:none (it is still safe to use offsets if a parent element is
+		// hidden; don safety goggles and see bug #4512 for more information).
+		div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
+		tds = div.getElementsByTagName("td");
+		tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
+		isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+		tds[ 0 ].style.display = "";
+		tds[ 1 ].style.display = "none";
+
+		// Support: IE8
+		// Check if empty table cells still have offsetWidth/Height
+		support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+		// Check box-sizing and margin behavior.
+		div.innerHTML = "";
+		div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
+
+		// Workaround failing boxSizing test due to offsetWidth returning wrong value
+		// with some non-1 values of body zoom, ticket #13543
+		jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() {
+			support.boxSizing = div.offsetWidth === 4;
+		});
+
+		// Use window.getComputedStyle because jsdom on node.js will break without it.
+		if ( window.getComputedStyle ) {
+			support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+			support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+			// Check if div with explicit width and no margin-right incorrectly
+			// gets computed margin-right based on width of container. (#3333)
+			// Fails in WebKit before Feb 2011 nightlies
+			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+			marginDiv = div.appendChild( document.createElement("div") );
+			marginDiv.style.cssText = div.style.cssText = divReset;
+			marginDiv.style.marginRight = marginDiv.style.width = "0";
+			div.style.width = "1px";
+
+			support.reliableMarginRight =
+				!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+		}
+
+		if ( typeof div.style.zoom !== core_strundefined ) {
+			// 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.innerHTML = "";
+			div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
+			support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+			// Support: IE6
+			// Check if elements with layout shrink-wrap their children
+			div.style.display = "block";
+			div.innerHTML = "<div></div>";
+			div.firstChild.style.width = "5px";
+			support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+
+			if ( support.inlineBlockNeedsLayout ) {
+				// 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 );
+
+		// Null elements to avoid leaks in IE
+		container = div = tds = marginDiv = null;
+	});
+
+	// Null elements to avoid leaks in IE
+	all = select = fragment = opt = a = input = null;
+
+	return support;
+})({});
+
+var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
+	rmultiDash = /([A-Z])/g;
+
+function internalData( elem, name, data, pvt /* Internal Use Only */ ){
+	if ( !jQuery.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 ] = core_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 ( !jQuery.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 ( jQuery.support.deleteExpando || cache != cache.window ) {
+		/* jshint eqeqeq: true */
+		delete cache[ id ];
+
+	// When all else fails, null
+	} else {
+		cache[ id ] = null;
+	}
+}
+
+jQuery.extend({
+	cache: {},
+
+	// The following elements throw uncatchable exceptions if you
+	// attempt to add expando properties to them.
+	noData: {
+		"applet": true,
+		"embed": true,
+		// Ban all objects except for Flash (which 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 );
+	},
+
+	// A method for determining if a DOM node can handle the data expando
+	acceptData: function( elem ) {
+		// Do not set data on non-element because it will not be cleared (#8335).
+		if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {
+			return false;
+		}
+
+		var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+		// nodes accept data unless otherwise specified; rejection can be conditional
+		return !noData || noData !== true && elem.getAttribute("classid") === noData;
+	}
+});
+
+jQuery.fn.extend({
+	data: function( key, value ) {
+		var attrs, name,
+			data = null,
+			i = 0,
+			elem = this[0];
+
+		// 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" ) ) {
+					attrs = elem.attributes;
+					for ( ; i < attrs.length; 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 ) ) : null;
+	},
+
+	removeData: function( key ) {
+		return this.each(function() {
+			jQuery.removeData( this, key );
+		});
+	}
+});
+
+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;
+}
+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 );
+		});
+	},
+	// Based off of the plugin by Clint Helfers, with permission.
+	// http://blindsignals.com/index.php/2009/07/jquery-delay/
+	delay: function( time, type ) {
+		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+		type = type || "fx";
+
+		return this.queue( type, function( next, hooks ) {
+			var timeout = setTimeout( next, time );
+			hooks.stop = function() {
+				clearTimeout( timeout );
+			};
+		});
+	},
+	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 );
+	}
+});
+var nodeHook, boolHook,
+	rclass = /[\t\r\n\f]/g,
+	rreturn = /\r/g,
+	rfocusable = /^(?:input|select|textarea|button|object)$/i,
+	rclickable = /^(?:a|area)$/i,
+	ruseDefault = /^(?:checked|selected)$/i,
+	getSetAttribute = jQuery.support.getSetAttribute,
+	getSetInput = jQuery.support.input;
+
+jQuery.fn.extend({
+	attr: function( name, value ) {
+		return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+	},
+
+	removeAttr: function( name ) {
+		return this.each(function() {
+			jQuery.removeAttr( this, name );
+		});
+	},
+
+	prop: function( name, value ) {
+		return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+	},
+
+	removeProp: function( name ) {
+		name = jQuery.propFix[ name ] || name;
+		return this.each(function() {
+			// try/catch handles cases where IE balks (such as removing a property on window)
+			try {
+				this[ name ] = undefined;
+				delete this[ name ];
+			} catch( e ) {}
+		});
+	},
+
+	addClass: function( value ) {
+		var classes, elem, cur, clazz, j,
+			i = 0,
+			len = this.length,
+			proceed = typeof value === "string" && value;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).addClass( value.call( this, j, this.className ) );
+			});
+		}
+
+		if ( proceed ) {
+			// The disjunction here is for better compressibility (see removeClass)
+			classes = ( value || "" ).match( core_rnotwhite ) || [];
+
+			for ( ; i < len; i++ ) {
+				elem = this[ i ];
+				cur = elem.nodeType === 1 && ( elem.className ?
+					( " " + elem.className + " " ).replace( rclass, " " ) :
+					" "
+				);
+
+				if ( cur ) {
+					j = 0;
+					while ( (clazz = classes[j++]) ) {
+						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+							cur += clazz + " ";
+						}
+					}
+					elem.className = jQuery.trim( cur );
+
+				}
+			}
+		}
+
+		return this;
+	},
+
+	removeClass: function( value ) {
+		var classes, elem, cur, clazz, j,
+			i = 0,
+			len = this.length,
+			proceed = arguments.length === 0 || typeof value === "string" && value;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).removeClass( value.call( this, j, this.className ) );
+			});
+		}
+		if ( proceed ) {
+			classes = ( value || "" ).match( core_rnotwhite ) || [];
+
+			for ( ; i < len; i++ ) {
+				elem = this[ i ];
+				// This expression is here for better compressibility (see addClass)
+				cur = elem.nodeType === 1 && ( elem.className ?
+					( " " + elem.className + " " ).replace( rclass, " " ) :
+					""
+				);
+
+				if ( cur ) {
+					j = 0;
+					while ( (clazz = classes[j++]) ) {
+						// Remove *all* instances
+						while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
+							cur = cur.replace( " " + clazz + " ", " " );
+						}
+					}
+					elem.className = value ? jQuery.trim( cur ) : "";
+				}
+			}
+		}
+
+		return this;
+	},
+
+	toggleClass: function( value, stateVal ) {
+		var type = typeof value;
+
+		if ( typeof stateVal === "boolean" && type === "string" ) {
+			return stateVal ? this.addClass( value ) : this.removeClass( value );
+		}
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( i ) {
+				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+			});
+		}
+
+		return this.each(function() {
+			if ( type === "string" ) {
+				// toggle individual class names
+				var className,
+					i = 0,
+					self = jQuery( this ),
+					classNames = value.match( core_rnotwhite ) || [];
+
+				while ( (className = classNames[ i++ ]) ) {
+					// check each className given, space separated list
+					if ( self.hasClass( className ) ) {
+						self.removeClass( className );
+					} else {
+						self.addClass( className );
+					}
+				}
+
+			// Toggle whole class name
+			} else if ( type === core_strundefined || type === "boolean" ) {
+				if ( this.className ) {
+					// store className if set
+					jQuery._data( this, "__className__", this.className );
+				}
+
+				// If the element has a class name or if we're passed "false",
+				// then remove the whole classname (if there was one, the above saved it).
+				// Otherwise bring back whatever was previously saved (if anything),
+				// falling back to the empty string if nothing was stored.
+				this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+			}
+		});
+	},
+
+	hasClass: function( selector ) {
+		var className = " " + selector + " ",
+			i = 0,
+			l = this.length;
+		for ( ; i < l; i++ ) {
+			if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+				return true;
+			}
+		}
+
+		return false;
+	},
+
+	val: function( value ) {
+		var ret, hooks, isFunction,
+			elem = this[0];
+
+		if ( !arguments.length ) {
+			if ( elem ) {
+				hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+					return ret;
+				}
+
+				ret = elem.value;
+
+				return typeof ret === "string" ?
+					// handle most common string cases
+					ret.replace(rreturn, "") :
+					// handle cases where value is null/undef or number
+					ret == null ? "" : ret;
+			}
+
+			return;
+		}
+
+		isFunction = jQuery.isFunction( value );
+
+		return this.each(function( i ) {
+			var val;
+
+			if ( this.nodeType !== 1 ) {
+				return;
+			}
+
+			if ( isFunction ) {
+				val = value.call( this, i, jQuery( this ).val() );
+			} else {
+				val = value;
+			}
+
+			// Treat null/undefined as ""; convert numbers to string
+			if ( val == null ) {
+				val = "";
+			} else if ( typeof val === "number" ) {
+				val += "";
+			} else if ( jQuery.isArray( val ) ) {
+				val = jQuery.map(val, function ( value ) {
+					return value == null ? "" : value + "";
+				});
+			}
+
+			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+			// If set returns undefined, fall back to normal setting
+			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+				this.value = val;
+			}
+		});
+	}
+});
+
+jQuery.extend({
+	valHooks: {
+		option: {
+			get: function( elem ) {
+				// Use proper attribute retrieval(#6932, #12072)
+				var val = jQuery.find.attr( elem, "value" );
+				return val != null ?
+					val :
+					elem.text;
+			}
+		},
+		select: {
+			get: function( elem ) {
+				var value, option,
+					options = elem.options,
+					index = elem.selectedIndex,
+					one = elem.type === "select-one" || index < 0,
+					values = one ? null : [],
+					max = one ? index + 1 : options.length,
+					i = index < 0 ?
+						max :
+						one ? index : 0;
+
+				// Loop through all the selected options
+				for ( ; i < max; i++ ) {
+					option = options[ i ];
+
+					// oldIE doesn't update selected after form reset (#2551)
+					if ( ( option.selected || i === index ) &&
+							// Don't return options that are disabled or in a disabled optgroup
+							( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
+							( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+						// Get the specific value for the option
+						value = jQuery( option ).val();
+
+						// We don't need an array for one selects
+						if ( one ) {
+							return value;
+						}
+
+						// Multi-Selects return an array
+						values.push( value );
+					}
+				}
+
+				return values;
+			},
+
+			set: function( elem, value ) {
+				var optionSet, option,
+					options = elem.options,
+					values = jQuery.makeArray( value ),
+					i = options.length;
+
+				while ( i-- ) {
+					option = options[ i ];
+					if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
+						optionSet = true;
+					}
+				}
+
+				// force browsers to behave consistently when non-matching value is set
+				if ( !optionSet ) {
+					elem.selectedIndex = -1;
+				}
+				return values;
+			}
+		}
+	},
+
+	attr: function( elem, name, value ) {
+		var hooks, ret,
+			nType = elem.nodeType;
+
+		// don't get/set attributes on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		// Fallback to prop when attributes are not supported
+		if ( typeof elem.getAttribute === core_strundefined ) {
+			return jQuery.prop( elem, name, value );
+		}
+
+		// All attributes are lowercase
+		// Grab necessary hook if one is defined
+		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+			name = name.toLowerCase();
+			hooks = jQuery.attrHooks[ name ] ||
+				( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
+		}
+
+		if ( value !== undefined ) {
+
+			if ( value === null ) {
+				jQuery.removeAttr( elem, name );
+
+			} else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				elem.setAttribute( name, value + "" );
+				return value;
+			}
+
+		} else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+			return ret;
+
+		} else {
+			ret = jQuery.find.attr( elem, name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return ret == null ?
+				undefined :
+				ret;
+		}
+	},
+
+	removeAttr: function( elem, value ) {
+		var name, propName,
+			i = 0,
+			attrNames = value && value.match( core_rnotwhite );
+
+		if ( attrNames && elem.nodeType === 1 ) {
+			while ( (name = attrNames[i++]) ) {
+				propName = jQuery.propFix[ name ] || name;
+
+				// Boolean attributes get special treatment (#10870)
+				if ( jQuery.expr.match.bool.test( name ) ) {
+					// Set corresponding property to false
+					if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
+						elem[ propName ] = false;
+					// Support: IE<9
+					// Also clear defaultChecked/defaultSelected (if appropriate)
+					} else {
+						elem[ jQuery.camelCase( "default-" + name ) ] =
+							elem[ propName ] = false;
+					}
+
+				// See #9699 for explanation of this approach (setting first, then removal)
+				} else {
+					jQuery.attr( elem, name, "" );
+				}
+
+				elem.removeAttribute( getSetAttribute ? name : propName );
+			}
+		}
+	},
+
+	attrHooks: {
+		type: {
+			set: function( elem, value ) {
+				if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+					// Setting the type on a radio button after the value resets the value in IE6-9
+					// Reset value to default in case type is set after value during creation
+					var val = elem.value;
+					elem.setAttribute( "type", value );
+					if ( val ) {
+						elem.value = val;
+					}
+					return value;
+				}
+			}
+		}
+	},
+
+	propFix: {
+		"for": "htmlFor",
+		"class": "className"
+	},
+
+	prop: function( elem, name, value ) {
+		var ret, hooks, notxml,
+			nType = elem.nodeType;
+
+		// don't get/set properties on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		if ( notxml ) {
+			// Fix name and attach hooks
+			name = jQuery.propFix[ name ] || name;
+			hooks = jQuery.propHooks[ name ];
+		}
+
+		if ( value !== undefined ) {
+			return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
+				ret :
+				( elem[ name ] = value );
+
+		} else {
+			return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
+				ret :
+				elem[ name ];
+		}
+	},
+
+	propHooks: {
+		tabIndex: {
+			get: function( elem ) {
+				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+				// Use proper attribute retrieval(#12072)
+				var tabindex = jQuery.find.attr( elem, "tabindex" );
+
+				return tabindex ?
+					parseInt( tabindex, 10 ) :
+					rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+						0 :
+						-1;
+			}
+		}
+	}
+});
+
+// Hooks for boolean attributes
+boolHook = {
+	set: function( elem, value, name ) {
+		if ( value === false ) {
+			// Remove boolean attributes when set to false
+			jQuery.removeAttr( elem, name );
+		} else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
+			// IE<8 needs the *property* name
+			elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );
+
+		// Use defaultChecked and defaultSelected for oldIE
+		} else {
+			elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
+		}
+
+		return name;
+	}
+};
+jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
+	var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr;
+
+	jQuery.expr.attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ?
+		function( elem, name, isXML ) {
+			var fn = jQuery.expr.attrHandle[ name ],
+				ret = isXML ?
+					undefined :
+					/* jshint eqeqeq: false */
+					(jQuery.expr.attrHandle[ name ] = undefined) !=
+						getter( elem, name, isXML ) ?
+
+						name.toLowerCase() :
+						null;
+			jQuery.expr.attrHandle[ name ] = fn;
+			return ret;
+		} :
+		function( elem, name, isXML ) {
+			return isXML ?
+				undefined :
+				elem[ jQuery.camelCase( "default-" + name ) ] ?
+					name.toLowerCase() :
+					null;
+		};
+});
+
+// fix oldIE attroperties
+if ( !getSetInput || !getSetAttribute ) {
+	jQuery.attrHooks.value = {
+		set: function( elem, value, name ) {
+			if ( jQuery.nodeName( elem, "input" ) ) {
+				// Does not return so that setAttribute is also used
+				elem.defaultValue = value;
+			} else {
+				// Use nodeHook if defined (#1954); otherwise setAttribute is fine
+				return nodeHook && nodeHook.set( elem, value, name );
+			}
+		}
+	};
+}
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+	// Use this for any attribute in IE6/7
+	// This fixes almost every IE6/7 issue
+	nodeHook = {
+		set: function( elem, value, name ) {
+			// Set the existing or create a new attribute node
+			var ret = elem.getAttributeNode( name );
+			if ( !ret ) {
+				elem.setAttributeNode(
+					(ret = elem.ownerDocument.createAttribute( name ))
+				);
+			}
+
+			ret.value = value += "";
+
+			// Break association with cloned elements by also using setAttribute (#9646)
+			return name === "value" || value === elem.getAttribute( name ) ?
+				value :
+				undefined;
+		}
+	};
+	jQuery.expr.attrHandle.id = jQuery.expr.attrHandle.name = jQuery.expr.attrHandle.coords =
+		// Some attributes are constructed with empty-string values when not defined
+		function( elem, name, isXML ) {
+			var ret;
+			return isXML ?
+				undefined :
+				(ret = elem.getAttributeNode( name )) && ret.value !== "" ?
+					ret.value :
+					null;
+		};
+	jQuery.valHooks.button = {
+		get: function( elem, name ) {
+			var ret = elem.getAttributeNode( name );
+			return ret && ret.specified ?
+				ret.value :
+				undefined;
+		},
+		set: nodeHook.set
+	};
+
+	// Set contenteditable to false on removals(#10429)
+	// Setting to empty string throws an error as an invalid value
+	jQuery.attrHooks.contenteditable = {
+		set: function( elem, value, name ) {
+			nodeHook.set( elem, value === "" ? false : value, name );
+		}
+	};
+
+	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
+	// This is for removals
+	jQuery.each([ "width", "height" ], function( i, name ) {
+		jQuery.attrHooks[ name ] = {
+			set: function( elem, value ) {
+				if ( value === "" ) {
+					elem.setAttribute( name, "auto" );
+					return value;
+				}
+			}
+		};
+	});
+}
+
+
+// Some attributes require a special call on IE
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !jQuery.support.hrefNormalized ) {
+	// href/src property should get the full normalized URL (#10299/#12915)
+	jQuery.each([ "href", "src" ], function( i, name ) {
+		jQuery.propHooks[ name ] = {
+			get: function( elem ) {
+				return elem.getAttribute( name, 4 );
+			}
+		};
+	});
+}
+
+if ( !jQuery.support.style ) {
+	jQuery.attrHooks.style = {
+		get: function( elem ) {
+			// Return undefined in the case of empty string
+			// Note: IE uppercases css property names, but if we were to .toLowerCase()
+			// .cssText, that would destroy case senstitivity in URL's, like in "background"
+			return elem.style.cssText || undefined;
+		},
+		set: function( elem, value ) {
+			return ( elem.style.cssText = value + "" );
+		}
+	};
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+	jQuery.propHooks.selected = {
+		get: function( elem ) {
+			var parent = elem.parentNode;
+
+			if ( parent ) {
+				parent.selectedIndex;
+
+				// Make sure that it also works with optgroups, see #5701
+				if ( parent.parentNode ) {
+					parent.parentNode.selectedIndex;
+				}
+			}
+			return null;
+		}
+	};
+}
+
+jQuery.each([
+	"tabIndex",
+	"readOnly",
+	"maxLength",
+	"cellSpacing",
+	"cellPadding",
+	"rowSpan",
+	"colSpan",
+	"useMap",
+	"frameBorder",
+	"contentEditable"
+], function() {
+	jQuery.propFix[ this.toLowerCase() ] = this;
+});
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+	jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+jQuery.each([ "radio", "checkbox" ], function() {
+	jQuery.valHooks[ this ] = {
+		set: function( elem, value ) {
+			if ( jQuery.isArray( value ) ) {
+				return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+			}
+		}
+	};
+	if ( !jQuery.support.checkOn ) {
+		jQuery.valHooks[ this ].get = function( elem ) {
+			// Support: Webkit
+			// "" is returned instead of "on" if a value isn't specified
+			return elem.getAttribute("value") === null ? "on" : elem.value;
+		};
+	}
+});
+var rformElems = /^(?:input|select|textarea)$/i,
+	rkeyEvent = /^key/,
+	rmouseEvent = /^(?:mouse|contextmenu)|click/,
+	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+	rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
+
+function returnTrue() {
+	return true;
+}
+
+function returnFalse() {
+	return false;
+}
+
+function safeActiveElement() {
+	try {
+		return document.activeElement;
+	} catch ( err ) { }
+}
+
+/*
+ * 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 !== core_strundefined && (!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( core_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( core_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 = core_hasOwn.call( event, "type" ) ? event.type : event,
+			namespaces = core_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(".") >= 0 ) {
+			// 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.namespace_re = 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 && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === 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) &&
+				jQuery.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, ret, handleObj, matched, j,
+			handlerQueue = [],
+			args = core_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.namespace_re || event.namespace_re.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 sel, handleObj, matches, i,
+			handlerQueue = [],
+			delegateCount = handlers.delegateCount,
+			cur = event.target;
+
+		// Find delegate handlers
+		// Black-hole SVG <use> instance trees (#13180)
+		// Avoid non-left-click bubbling in Firefox (#3861)
+		if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
+
+			/* 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 ) >= 0 :
+								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: Chrome 23+, Safari?
+		// 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 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 ) {
+
+				// Even when returnValue equals to undefined Firefox will still show alert
+				if ( event.result !== undefined ) {
+					event.originalEvent.returnValue = event.result;
+				}
+			}
+		}
+	},
+
+	simulate: function( type, elem, event, bubble ) {
+		// Piggyback on a donor event to simulate a different one.
+		// Fake originalEvent to avoid donor's stopPropagation, but if the
+		// simulated event prevents default then we do the same on the donor.
+		var e = jQuery.extend(
+			new jQuery.Event(),
+			event,
+			{
+				type: type,
+				isSimulated: true,
+				originalEvent: {}
+			}
+		);
+		if ( bubble ) {
+			jQuery.event.trigger( e, null, elem );
+		} else {
+			jQuery.event.dispatch.call( elem, e );
+		}
+		if ( e.isDefaultPrevented() ) {
+			event.preventDefault();
+		}
+	}
+};
+
+jQuery.removeEvent = document.removeEventListener ?
+	function( elem, type, handle ) {
+		if ( elem.removeEventListener ) {
+			elem.removeEventListener( type, handle, false );
+		}
+	} :
+	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 ] === core_strundefined ) {
+				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.returnValue === false ||
+			src.getPreventDefault && src.getPreventDefault() ) ? 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 = {
+	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 ) {
+			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() {
+		this.isImmediatePropagationStopped = returnTrue;
+		this.stopPropagation();
+	}
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+	mouseenter: "mouseover",
+	mouseleave: "mouseout"
+}, 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 mousenter/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 ( !jQuery.support.submitBubbles ) {
+
+	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" ) ? elem.form : undefined;
+				if ( form && !jQuery._data( form, "submitBubbles" ) ) {
+					jQuery.event.add( form, "submit._submit", function( event ) {
+						event._submit_bubble = true;
+					});
+					jQuery._data( form, "submitBubbles", 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._submit_bubble ) {
+				delete event._submit_bubble;
+				if ( this.parentNode && !event.isTrigger ) {
+					jQuery.event.simulate( "submit", this.parentNode, event, true );
+				}
+			}
+		},
+
+		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 ( !jQuery.support.changeBubbles ) {
+
+	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._just_changed = true;
+						}
+					});
+					jQuery.event.add( this, "click._change", function( event ) {
+						if ( this._just_changed && !event.isTrigger ) {
+							this._just_changed = false;
+						}
+						// Allow triggered, simulated change events (#11500)
+						jQuery.event.simulate( "change", this, event, true );
+					});
+				}
+				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, "changeBubbles" ) ) {
+					jQuery.event.add( elem, "change._change", function( event ) {
+						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+							jQuery.event.simulate( "change", this.parentNode, event, true );
+						}
+					});
+					jQuery._data( elem, "changeBubbles", 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 );
+		}
+	};
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+		// Attach a single capturing handler while someone wants focusin/focusout
+		var attaches = 0,
+			handler = function( event ) {
+				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+			};
+
+		jQuery.event.special[ fix ] = {
+			setup: function() {
+				if ( attaches++ === 0 ) {
+					document.addEventListener( orig, handler, true );
+				}
+			},
+			teardown: function() {
+				if ( --attaches === 0 ) {
+					document.removeEventListener( orig, handler, true );
+				}
+			}
+		};
+	});
+}
+
+jQuery.fn.extend({
+
+	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+		var type, origFn;
+
+		// 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 ) {
+				this.on( type, selector, data, types[ type ], one );
+			}
+			return this;
+		}
+
+		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 this;
+		}
+
+		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 this.each( function() {
+			jQuery.event.add( this, types, fn, data, selector );
+		});
+	},
+	one: function( types, selector, data, fn ) {
+		return this.on( 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 isSimple = /^.[^:#\[\.,]*$/,
+	rparentsprev = /^(?:parents|prev(?:Until|All))/,
+	rneedsContext = jQuery.expr.match.needsContext,
+	// 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({
+	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;
+	},
+
+	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;
+				}
+			}
+		});
+	},
+
+	not: function( selector ) {
+		return this.pushStack( winnow(this, selector || [], true) );
+	},
+
+	filter: function( selector ) {
+		return this.pushStack( winnow(this, selector || [], false) );
+	},
+
+	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;
+	},
+
+	closest: function( selectors, context ) {
+		var cur,
+			i = 0,
+			l = this.length,
+			ret = [],
+			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)) ) {
+
+					cur = ret.push( cur );
+					break;
+				}
+			}
+		}
+
+		return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret );
+	},
+
+	// 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 ) {
+		var set = typeof selector === "string" ?
+				jQuery( selector, context ) :
+				jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+			all = jQuery.merge( this.get(), set );
+
+		return this.pushStack( jQuery.unique(all) );
+	},
+
+	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 jQuery.dir( elem, "parentNode" );
+	},
+	parentsUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "parentNode", until );
+	},
+	next: function( elem ) {
+		return sibling( elem, "nextSibling" );
+	},
+	prev: function( elem ) {
+		return sibling( elem, "previousSibling" );
+	},
+	nextAll: function( elem ) {
+		return jQuery.dir( elem, "nextSibling" );
+	},
+	prevAll: function( elem ) {
+		return jQuery.dir( elem, "previousSibling" );
+	},
+	nextUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "nextSibling", until );
+	},
+	prevUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "previousSibling", until );
+	},
+	siblings: function( elem ) {
+		return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+	},
+	children: function( elem ) {
+		return jQuery.sibling( 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.unique( ret );
+			}
+
+			// Reverse order for parents* and prev-derivatives
+			if ( rparentsprev.test( name ) ) {
+				ret = ret.reverse();
+			}
+		}
+
+		return this.pushStack( ret );
+	};
+});
+
+jQuery.extend({
+	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;
+			}));
+	},
+
+	dir: function( elem, dir, until ) {
+		var matched = [],
+			cur = elem[ dir ];
+
+		while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+			if ( cur.nodeType === 1 ) {
+				matched.push( cur );
+			}
+			cur = cur[dir];
+		}
+		return matched;
+	},
+
+	sibling: function( n, elem ) {
+		var r = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType === 1 && n !== elem ) {
+				r.push( n );
+			}
+		}
+
+		return r;
+	}
+});
+
+// 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 ( isSimple.test( qualifier ) ) {
+			return jQuery.filter( qualifier, elements, not );
+		}
+
+		qualifier = jQuery.filter( qualifier, elements );
+	}
+
+	return jQuery.grep( elements, function( elem ) {
+		return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not;
+	});
+}
+function createSafeFragment( document ) {
+	var list = nodeNames.split( "|" ),
+		safeFrag = document.createDocumentFragment();
+
+	if ( safeFrag.createElement ) {
+		while ( list.length ) {
+			safeFrag.createElement(
+				list.pop()
+			);
+		}
+	}
+	return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
+		"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+	rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
+	rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
+	rleadingWhitespace = /^\s+/,
+	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+	rtagName = /<([\w:]+)/,
+	rtbody = /<tbody/i,
+	rhtml = /<|&#?\w+;/,
+	rnoInnerhtml = /<(?:script|style|link)/i,
+	manipulation_rcheckableType = /^(?:checkbox|radio)$/i,
+	// checked="checked" or checked
+	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+	rscriptType = /^$|\/(?:java|ecma)script/i,
+	rscriptTypeMasked = /^true\/(.*)/,
+	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
+
+	// We have to close these tags to support XHTML (#13200)
+	wrapMap = {
+		option: [ 1, "<select multiple='multiple'>", "</select>" ],
+		legend: [ 1, "<fieldset>", "</fieldset>" ],
+		area: [ 1, "<map>", "</map>" ],
+		param: [ 1, "<object>", "</object>" ],
+		thead: [ 1, "<table>", "</table>" ],
+		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+		col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+
+		// 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: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>"  ]
+	},
+	safeFragment = createSafeFragment( document ),
+	fragmentDiv = safeFragment.appendChild( document.createElement("div") );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+jQuery.fn.extend({
+	text: function( value ) {
+		return jQuery.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 this.domManip( arguments, function( elem ) {
+			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+				var target = manipulationTarget( this, elem );
+				target.appendChild( elem );
+			}
+		});
+	},
+
+	prepend: function() {
+		return this.domManip( 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 this.domManip( arguments, function( elem ) {
+			if ( this.parentNode ) {
+				this.parentNode.insertBefore( elem, this );
+			}
+		});
+	},
+
+	after: function() {
+		return this.domManip( arguments, function( elem ) {
+			if ( this.parentNode ) {
+				this.parentNode.insertBefore( elem, this.nextSibling );
+			}
+		});
+	},
+
+	// keepData is for internal use only--do not document
+	remove: function( selector, keepData ) {
+		var elem,
+			elems = selector ? jQuery.filter( selector, this ) : this,
+			i = 0;
+
+		for ( ; (elem = elems[i]) != null; i++ ) {
+
+			if ( !keepData && elem.nodeType === 1 ) {
+				jQuery.cleanData( getAll( elem ) );
+			}
+
+			if ( elem.parentNode ) {
+				if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
+					setGlobalEval( getAll( elem, "script" ) );
+				}
+				elem.parentNode.removeChild( elem );
+			}
+		}
+
+		return this;
+	},
+
+	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 jQuery.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 ) &&
+				( jQuery.support.htmlSerialize || !rnoshimcache.test( value )  ) &&
+				( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
+				!wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
+
+				value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+				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
+			// Snapshot the DOM in case .domManip sweeps something relevant into its fragment
+			args = jQuery.map( this, function( elem ) {
+				return [ elem.nextSibling, elem.parentNode ];
+			}),
+			i = 0;
+
+		// Make the changes, replacing each context element with the new content
+		this.domManip( arguments, function( elem ) {
+			var next = args[ i++ ],
+				parent = args[ i++ ];
+
+			if ( parent ) {
+				// Don't use the snapshot next if it has moved (#13810)
+				if ( next && next.parentNode !== parent ) {
+					next = this.nextSibling;
+				}
+				jQuery( this ).remove();
+				parent.insertBefore( elem, next );
+			}
+		// Allow new content to include elements from the context set
+		}, true );
+
+		// Force removal if there was no new content (e.g., from empty arguments)
+		return i ? this : this.remove();
+	},
+
+	detach: function( selector ) {
+		return this.remove( selector, true );
+	},
+
+	domManip: function( args, callback, allowIntersection ) {
+
+		// Flatten any nested arrays
+		args = core_concat.apply( [], args );
+
+		var first, node, hasScripts,
+			scripts, doc, fragment,
+			i = 0,
+			l = this.length,
+			set = this,
+			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" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {
+			return this.each(function( index ) {
+				var self = set.eq( index );
+				if ( isFunction ) {
+					args[0] = value.call( this, index, self.html() );
+				}
+				self.domManip( args, callback, allowIntersection );
+			});
+		}
+
+		if ( l ) {
+			fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this );
+			first = fragment.firstChild;
+
+			if ( fragment.childNodes.length === 1 ) {
+				fragment = first;
+			}
+
+			if ( first ) {
+				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 ) {
+							jQuery.merge( scripts, getAll( node, "script" ) );
+						}
+					}
+
+					callback.call( this[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 ) {
+								// Hope ajax is available...
+								jQuery._evalUrl( node.src );
+							} else {
+								jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );
+							}
+						}
+					}
+				}
+
+				// Fix #11809: Avoid leaking memory
+				fragment = first = null;
+			}
+		}
+
+		return this;
+	}
+});
+
+// Support: IE<8
+// Manipulating tables requires a tbody
+function manipulationTarget( elem, content ) {
+	return jQuery.nodeName( elem, "table" ) &&
+		jQuery.nodeName( content.nodeType === 1 ? 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;
+}
+
+// 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" ) );
+	}
+}
+
+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 ( !jQuery.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 ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {
+			dest.innerHTML = src.innerHTML;
+		}
+
+	} else if ( nodeName === "input" && manipulation_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;
+	}
+}
+
+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()
+			core_push.apply( ret, elems.get() );
+		}
+
+		return this.pushStack( ret );
+	};
+});
+
+function getAll( context, tag ) {
+	var elems, elem,
+		i = 0,
+		found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) :
+			typeof context.querySelectorAll !== core_strundefined ? 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;
+}
+
+// Used in buildFragment, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+	if ( manipulation_rcheckableType.test( elem.type ) ) {
+		elem.defaultChecked = elem.checked;
+	}
+}
+
+jQuery.extend({
+	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+		var destElements, node, clone, i, srcElements,
+			inPage = jQuery.contains( elem.ownerDocument, elem );
+
+		if ( jQuery.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 ( (!jQuery.support.noCloneEvent || !jQuery.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;
+	},
+
+	buildFragment: function( elems, context, scripts, selection ) {
+		var j, elem, contains,
+			tmp, tag, tbody, wrap,
+			l = elems.length,
+
+			// Ensure a safe fragment
+			safe = createSafeFragment( context ),
+
+			nodes = [],
+			i = 0;
+
+		for ( ; i < l; i++ ) {
+			elem = elems[ i ];
+
+			if ( elem || elem === 0 ) {
+
+				// Add nodes directly
+				if ( jQuery.type( elem ) === "object" ) {
+					jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+				// Convert non-html into a text node
+				} else if ( !rhtml.test( elem ) ) {
+					nodes.push( context.createTextNode( elem ) );
+
+				// Convert html into DOM nodes
+				} else {
+					tmp = tmp || safe.appendChild( context.createElement("div") );
+
+					// Deserialize a standard representation
+					tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
+					wrap = wrapMap[ tag ] || wrapMap._default;
+
+					tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[2];
+
+					// Descend through wrappers to the right content
+					j = wrap[0];
+					while ( j-- ) {
+						tmp = tmp.lastChild;
+					}
+
+					// Manually add leading whitespace removed by IE
+					if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+						nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );
+					}
+
+					// Remove IE's autoinserted <tbody> from table fragments
+					if ( !jQuery.support.tbody ) {
+
+						// String was a <table>, *may* have spurious <tbody>
+						elem = tag === "table" && !rtbody.test( elem ) ?
+							tmp.firstChild :
+
+							// String was a bare <thead> or <tfoot>
+							wrap[1] === "<table>" && !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 ( !jQuery.support.appendChecked ) {
+			jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked );
+		}
+
+		i = 0;
+		while ( (elem = nodes[ i++ ]) ) {
+
+			// #4087 - If origin and destination elements are the same, and this is
+			// that element, do not do anything
+			if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
+				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;
+	},
+
+	cleanData: function( elems, /* internal */ acceptData ) {
+		var elem, type, id, data,
+			i = 0,
+			internalKey = jQuery.expando,
+			cache = jQuery.cache,
+			deleteExpando = jQuery.support.deleteExpando,
+			special = jQuery.event.special;
+
+		for ( ; (elem = elems[i]) != null; i++ ) {
+
+			if ( acceptData || jQuery.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 ];
+
+						// IE does not allow us to delete expando properties from nodes,
+						// nor does it have a removeAttribute function on Document nodes;
+						// we must handle all of these cases
+						if ( deleteExpando ) {
+							delete elem[ internalKey ];
+
+						} else if ( typeof elem.removeAttribute !== core_strundefined ) {
+							elem.removeAttribute( internalKey );
+
+						} else {
+							elem[ internalKey ] = null;
+						}
+
+						core_deletedIds.push( id );
+					}
+				}
+			}
+		}
+	},
+
+	_evalUrl: function( url ) {
+		return jQuery.ajax({
+			url: url,
+			type: "GET",
+			dataType: "script",
+			async: false,
+			global: false,
+			"throws": true
+		});
+	}
+});
+jQuery.fn.extend({
+	wrapAll: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapAll( html.call(this, i) );
+			});
+		}
+
+		if ( this[0] ) {
+			// The elements to wrap the target around
+			var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+			if ( this[0].parentNode ) {
+				wrap.insertBefore( this[0] );
+			}
+
+			wrap.map(function() {
+				var elem = this;
+
+				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+					elem = elem.firstChild;
+				}
+
+				return elem;
+			}).append( this );
+		}
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapInner( html.call(this, i) );
+			});
+		}
+
+		return this.each(function() {
+			var self = jQuery( this ),
+				contents = self.contents();
+
+			if ( contents.length ) {
+				contents.wrapAll( html );
+
+			} else {
+				self.append( html );
+			}
+		});
+	},
+
+	wrap: function( html ) {
+		var isFunction = jQuery.isFunction( html );
+
+		return this.each(function(i) {
+			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+		});
+	},
+
+	unwrap: function() {
+		return this.parent().each(function() {
+			if ( !jQuery.nodeName( this, "body" ) ) {
+				jQuery( this ).replaceWith( this.childNodes );
+			}
+		}).end();
+	}
+});
+var iframe, getStyles, curCSS,
+	ralpha = /alpha\([^)]*\)/i,
+	ropacity = /opacity\s*=\s*([^)]*)/,
+	rposition = /^(top|right|bottom|left)$/,
+	// swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
+	// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+	rmargin = /^margin/,
+	rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
+	rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
+	rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ),
+	elemdisplay = { BODY: "block" },
+
+	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+	cssNormalTransform = {
+		letterSpacing: 0,
+		fontWeight: 400
+	},
+
+	cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
+
+// return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
+
+	// shortcut for names that are not vendor prefixed
+	if ( name in style ) {
+		return name;
+	}
+
+	// check for vendor prefixed names
+	var capName = name.charAt(0).toUpperCase() + name.slice(1),
+		origName = name,
+		i = cssPrefixes.length;
+
+	while ( i-- ) {
+		name = cssPrefixes[ i ] + capName;
+		if ( name in style ) {
+			return name;
+		}
+	}
+
+	return origName;
+}
+
+function isHidden( 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 showHide( elements, show ) {
+	var display, elem, hidden,
+		values = [],
+		index = 0,
+		length = elements.length;
+
+	for ( ; index < length; index++ ) {
+		elem = elements[ index ];
+		if ( !elem.style ) {
+			continue;
+		}
+
+		values[ index ] = jQuery._data( elem, "olddisplay" );
+		display = elem.style.display;
+		if ( show ) {
+			// Reset the inline display of this element to learn if it is
+			// being hidden by cascaded rules or not
+			if ( !values[ index ] && display === "none" ) {
+				elem.style.display = "";
+			}
+
+			// Set elements which have been overridden with display: none
+			// in a stylesheet to whatever the default browser style is
+			// for such an element
+			if ( elem.style.display === "" && isHidden( elem ) ) {
+				values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
+			}
+		} else {
+
+			if ( !values[ index ] ) {
+				hidden = isHidden( elem );
+
+				if ( display && display !== "none" || !hidden ) {
+					jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
+				}
+			}
+		}
+	}
+
+	// Set the display of most of the elements in a second loop
+	// to avoid the constant reflow
+	for ( index = 0; index < length; index++ ) {
+		elem = elements[ index ];
+		if ( !elem.style ) {
+			continue;
+		}
+		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+			elem.style.display = show ? values[ index ] || "" : "none";
+		}
+	}
+
+	return elements;
+}
+
+jQuery.fn.extend({
+	css: function( name, value ) {
+		return jQuery.access( this, function( elem, name, value ) {
+			var len, styles,
+				map = {},
+				i = 0;
+
+			if ( jQuery.isArray( name ) ) {
+				styles = getStyles( elem );
+				len = name.length;
+
+				for ( ; i < len; i++ ) {
+					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+				}
+
+				return map;
+			}
+
+			return value !== undefined ?
+				jQuery.style( elem, name, value ) :
+				jQuery.css( elem, name );
+		}, name, value, arguments.length > 1 );
+	},
+	show: function() {
+		return showHide( this, true );
+	},
+	hide: function() {
+		return showHide( this );
+	},
+	toggle: function( state ) {
+		if ( typeof state === "boolean" ) {
+			return state ? this.show() : this.hide();
+		}
+
+		return this.each(function() {
+			if ( isHidden( this ) ) {
+				jQuery( this ).show();
+			} else {
+				jQuery( this ).hide();
+			}
+		});
+	}
+});
+
+jQuery.extend({
+	// Add in style property hooks for overriding the default
+	// behavior of getting and setting a style property
+	cssHooks: {
+		opacity: {
+			get: function( elem, computed ) {
+				if ( computed ) {
+					// We should always get a number back from opacity
+					var ret = curCSS( elem, "opacity" );
+					return ret === "" ? "1" : ret;
+				}
+			}
+		}
+	},
+
+	// Don't automatically add "px" to these possibly-unitless properties
+	cssNumber: {
+		"columnCount": true,
+		"fillOpacity": true,
+		"fontWeight": true,
+		"lineHeight": true,
+		"opacity": true,
+		"order": true,
+		"orphans": true,
+		"widows": true,
+		"zIndex": true,
+		"zoom": true
+	},
+
+	// Add in properties whose names you wish to fix before
+	// setting or getting the value
+	cssProps: {
+		// normalize float css property
+		"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+	},
+
+	// Get and set the style property on a DOM Node
+	style: function( elem, name, value, extra ) {
+		// Don't set styles on text and comment nodes
+		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+			return;
+		}
+
+		// Make sure that we're working with the right name
+		var ret, type, hooks,
+			origName = jQuery.camelCase( name ),
+			style = elem.style;
+
+		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+		// gets hook for the prefixed version
+		// followed by the unprefixed version
+		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+		// Check if we're setting a value
+		if ( value !== undefined ) {
+			type = typeof value;
+
+			// convert relative number strings (+= or -=) to relative numbers. #7345
+			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+				value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+				// Fixes bug #9237
+				type = "number";
+			}
+
+			// Make sure that NaN and null values aren't set. See: #7116
+			if ( value == null || type === "number" && isNaN( value ) ) {
+				return;
+			}
+
+			// If a number was passed in, add 'px' to the (except for certain CSS properties)
+			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+				value += "px";
+			}
+
+			// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
+			// but it would mean to define eight (for every problematic property) identical functions
+			if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
+				style[ name ] = "inherit";
+			}
+
+			// If a hook was provided, use that value, otherwise just set the specified value
+			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+
+				// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+				// Fixes bug #5509
+				try {
+					style[ name ] = value;
+				} catch(e) {}
+			}
+
+		} else {
+			// If a hook was provided get the non-computed value from there
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+				return ret;
+			}
+
+			// Otherwise just get the value from the style object
+			return style[ name ];
+		}
+	},
+
+	css: function( elem, name, extra, styles ) {
+		var num, val, hooks,
+			origName = jQuery.camelCase( name );
+
+		// Make sure that we're working with the right name
+		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+		// gets hook for the prefixed version
+		// followed by the unprefixed version
+		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+		// If a hook was provided get the computed value from there
+		if ( hooks && "get" in hooks ) {
+			val = hooks.get( elem, true, extra );
+		}
+
+		// Otherwise, if a way to get the computed value exists, use that
+		if ( val === undefined ) {
+			val = curCSS( elem, name, styles );
+		}
+
+		//convert "normal" to computed value
+		if ( val === "normal" && name in cssNormalTransform ) {
+			val = cssNormalTransform[ name ];
+		}
+
+		// Return, converting to number if forced or a qualifier was provided and val looks numeric
+		if ( extra === "" || extra ) {
+			num = parseFloat( val );
+			return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
+		}
+		return val;
+	}
+});
+
+// NOTE: we've included the "window" in window.getComputedStyle
+// because jsdom on node.js will break without it.
+if ( window.getComputedStyle ) {
+	getStyles = function( elem ) {
+		return window.getComputedStyle( elem, null );
+	};
+
+	curCSS = function( elem, name, _computed ) {
+		var width, minWidth, maxWidth,
+			computed = _computed || getStyles( elem ),
+
+			// getPropertyValue is only needed for .css('filter') in IE9, see #12537
+			ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,
+			style = elem.style;
+
+		if ( computed ) {
+
+			if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+				ret = jQuery.style( elem, name );
+			}
+
+			// A tribute to the "awesome hack by Dean Edwards"
+			// Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
+			// Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+			// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+			if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+
+				// Remember the original values
+				width = style.width;
+				minWidth = style.minWidth;
+				maxWidth = style.maxWidth;
+
+				// Put in the new values to get a computed value out
+				style.minWidth = style.maxWidth = style.width = ret;
+				ret = computed.width;
+
+				// Revert the changed values
+				style.width = width;
+				style.minWidth = minWidth;
+				style.maxWidth = maxWidth;
+			}
+		}
+
+		return ret;
+	};
+} else if ( document.documentElement.currentStyle ) {
+	getStyles = function( elem ) {
+		return elem.currentStyle;
+	};
+
+	curCSS = function( elem, name, _computed ) {
+		var left, rs, rsLeft,
+			computed = _computed || getStyles( elem ),
+			ret = computed ? computed[ name ] : undefined,
+			style = elem.style;
+
+		// Avoid setting ret to empty string here
+		// so we don't default to auto
+		if ( ret == null && style && style[ name ] ) {
+			ret = style[ name ];
+		}
+
+		// From the awesome hack by Dean Edwards
+		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+		// If we're not dealing with a regular pixel number
+		// but a number that has a weird ending, we need to convert it to pixels
+		// but not position css attributes, as those are proportional to the parent element instead
+		// and we can't measure the parent instead because it might trigger a "stacking dolls" problem
+		if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
+
+			// Remember the original values
+			left = style.left;
+			rs = elem.runtimeStyle;
+			rsLeft = rs && rs.left;
+
+			// Put in the new values to get a computed value out
+			if ( rsLeft ) {
+				rs.left = elem.currentStyle.left;
+			}
+			style.left = name === "fontSize" ? "1em" : ret;
+			ret = style.pixelLeft + "px";
+
+			// Revert the changed values
+			style.left = left;
+			if ( rsLeft ) {
+				rs.left = rsLeft;
+			}
+		}
+
+		return ret === "" ? "auto" : ret;
+	};
+}
+
+function setPositiveNumber( elem, value, subtract ) {
+	var matches = rnumsplit.exec( value );
+	return matches ?
+		// Guard against undefined "subtract", e.g., when used as in cssHooks
+		Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+		value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
+	var i = extra === ( isBorderBox ? "border" : "content" ) ?
+		// If we already have the right measurement, avoid augmentation
+		4 :
+		// Otherwise initialize for horizontal or vertical properties
+		name === "width" ? 1 : 0,
+
+		val = 0;
+
+	for ( ; i < 4; i += 2 ) {
+		// both box models exclude margin, so add it if we want it
+		if ( extra === "margin" ) {
+			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
+		}
+
+		if ( isBorderBox ) {
+			// border-box includes padding, so remove it if we want content
+			if ( extra === "content" ) {
+				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+			}
+
+			// at this point, extra isn't border nor margin, so remove border
+			if ( extra !== "margin" ) {
+				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+			}
+		} else {
+			// at this point, extra isn't content, so add padding
+			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+			// at this point, extra isn't content nor padding, so add border
+			if ( extra !== "padding" ) {
+				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+			}
+		}
+	}
+
+	return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+	// Start with offset property, which is equivalent to the border-box value
+	var valueIsBorderBox = true,
+		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+		styles = getStyles( elem ),
+		isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+	// some non-html elements return undefined for offsetWidth, so check for null/undefined
+	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+	if ( val <= 0 || val == null ) {
+		// Fall back to computed then uncomputed css if necessary
+		val = curCSS( elem, name, styles );
+		if ( val < 0 || val == null ) {
+			val = elem.style[ name ];
+		}
+
+		// Computed unit is not pixels. Stop here and return.
+		if ( rnumnonpx.test(val) ) {
+			return val;
+		}
+
+		// we need the check for style in case a browser which returns unreliable values
+		// for getComputedStyle silently falls back to the reliable elem.style
+		valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
+
+		// Normalize "", auto, and prepare for extra
+		val = parseFloat( val ) || 0;
+	}
+
+	// use the active box-sizing model to add/subtract irrelevant styles
+	return ( val +
+		augmentWidthOrHeight(
+			elem,
+			name,
+			extra || ( isBorderBox ? "border" : "content" ),
+			valueIsBorderBox,
+			styles
+		)
+	) + "px";
+}
+
+// Try to determine the default display value of an element
+function css_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("<iframe frameborder='0' width='0' height='0'/>")
+				.css( "cssText", "display:block !important" )
+			).appendTo( doc.documentElement );
+
+			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
+			doc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document;
+			doc.write("<!doctype html><html><body>");
+			doc.close();
+
+			display = actualDisplay( nodeName, doc );
+			iframe.detach();
+		}
+
+		// Store the correct default display
+		elemdisplay[ nodeName ] = display;
+	}
+
+	return display;
+}
+
+// Called ONLY from within css_defaultDisplay
+function actualDisplay( name, doc ) {
+	var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
+		display = jQuery.css( elem[0], "display" );
+	elem.remove();
+	return display;
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+	jQuery.cssHooks[ name ] = {
+		get: function( elem, computed, extra ) {
+			if ( computed ) {
+				// certain elements can have dimension info if we invisibly show them
+				// however, it must have a current display style that would benefit from this
+				return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ?
+					jQuery.swap( elem, cssShow, function() {
+						return getWidthOrHeight( elem, name, extra );
+					}) :
+					getWidthOrHeight( elem, name, extra );
+			}
+		},
+
+		set: function( elem, value, extra ) {
+			var styles = extra && getStyles( elem );
+			return setPositiveNumber( elem, value, extra ?
+				augmentWidthOrHeight(
+					elem,
+					name,
+					extra,
+					jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+					styles
+				) : 0
+			);
+		}
+	};
+});
+
+if ( !jQuery.support.opacity ) {
+	jQuery.cssHooks.opacity = {
+		get: function( elem, computed ) {
+			// IE uses filters for opacity
+			return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+				( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
+				computed ? "1" : "";
+		},
+
+		set: function( elem, value ) {
+			var style = elem.style,
+				currentStyle = elem.currentStyle,
+				opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+				filter = currentStyle && currentStyle.filter || style.filter || "";
+
+			// IE has trouble with opacity if it does not have layout
+			// Force it by setting the zoom level
+			style.zoom = 1;
+
+			// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+			// if value === "", then remove inline opacity #12685
+			if ( ( value >= 1 || value === "" ) &&
+					jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
+					style.removeAttribute ) {
+
+				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+				// if "filter:" is present at all, clearType is disabled, we want to avoid this
+				// style.removeAttribute is IE Only, but so apparently is this code path...
+				style.removeAttribute( "filter" );
+
+				// if there is no filter style applied in a css rule or unset inline opacity, we are done
+				if ( value === "" || currentStyle && !currentStyle.filter ) {
+					return;
+				}
+			}
+
+			// otherwise, set new filter values
+			style.filter = ralpha.test( filter ) ?
+				filter.replace( ralpha, opacity ) :
+				filter + " " + opacity;
+		}
+	};
+}
+
+// These hooks cannot be added until DOM ready because the support test
+// for it is not run until after DOM ready
+jQuery(function() {
+	if ( !jQuery.support.reliableMarginRight ) {
+		jQuery.cssHooks.marginRight = {
+			get: function( elem, computed ) {
+				if ( computed ) {
+					// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+					// Work around by temporarily setting element display to inline-block
+					return jQuery.swap( elem, { "display": "inline-block" },
+						curCSS, [ elem, "marginRight" ] );
+				}
+			}
+		};
+	}
+
+	// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+	// getComputedStyle returns percent when specified for top/left/bottom/right
+	// rather than make the css module depend on the offset module, we just check for it here
+	if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
+		jQuery.each( [ "top", "left" ], function( i, prop ) {
+			jQuery.cssHooks[ prop ] = {
+				get: function( elem, computed ) {
+					if ( computed ) {
+						computed = curCSS( elem, prop );
+						// if curCSS returns percentage, fallback to offset
+						return rnumnonpx.test( computed ) ?
+							jQuery( elem ).position()[ prop ] + "px" :
+							computed;
+					}
+				}
+			};
+		});
+	}
+
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.hidden = function( elem ) {
+		// Support: Opera <= 12.12
+		// Opera reports offsetWidths and offsetHeights less than zero on some elements
+		return elem.offsetWidth <= 0 && elem.offsetHeight <= 0 ||
+			(!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
+	};
+
+	jQuery.expr.filters.visible = function( elem ) {
+		return !jQuery.expr.filters.hidden( elem );
+	};
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+	margin: "",
+	padding: "",
+	border: "Width"
+}, function( prefix, suffix ) {
+	jQuery.cssHooks[ prefix + suffix ] = {
+		expand: function( value ) {
+			var i = 0,
+				expanded = {},
+
+				// assumes a single number if not a string
+				parts = typeof value === "string" ? value.split(" ") : [ value ];
+
+			for ( ; i < 4; i++ ) {
+				expanded[ prefix + cssExpand[ i ] + suffix ] =
+					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+			}
+
+			return expanded;
+		}
+	};
+
+	if ( !rmargin.test( prefix ) ) {
+		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+	}
+});
+var r20 = /%20/g,
+	rbracket = /\[\]$/,
+	rCRLF = /\r?\n/g,
+	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+	rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+jQuery.fn.extend({
+	serialize: function() {
+		return jQuery.param( this.serializeArray() );
+	},
+	serializeArray: function() {
+		return this.map(function(){
+			// Can add propHook for "elements" to filter or add form elements
+			var elements = jQuery.prop( this, "elements" );
+			return elements ? jQuery.makeArray( elements ) : this;
+		})
+		.filter(function(){
+			var type = this.type;
+			// Use .is(":disabled") so that fieldset[disabled] works
+			return this.name && !jQuery( this ).is( ":disabled" ) &&
+				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+				( this.checked || !manipulation_rcheckableType.test( type ) );
+		})
+		.map(function( i, elem ){
+			var val = jQuery( this ).val();
+
+			return val == null ?
+				null :
+				jQuery.isArray( val ) ?
+					jQuery.map( val, function( val ){
+						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+					}) :
+					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+		}).get();
+	}
+});
+
+//Serialize an array of form elements or a set of
+//key/values into a query string
+jQuery.param = function( a, traditional ) {
+	var prefix,
+		s = [],
+		add = function( key, value ) {
+			// If value is a function, invoke it and return its value
+			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+		};
+
+	// Set traditional to true for jQuery <= 1.3.2 behavior.
+	if ( traditional === undefined ) {
+		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+	}
+
+	// If an array was passed in, assume that it is an array of form elements.
+	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+		// Serialize the form elements
+		jQuery.each( a, function() {
+			add( this.name, this.value );
+		});
+
+	} else {
+		// If traditional, encode the "old" way (the way 1.3.2 or older
+		// did it), otherwise encode params recursively.
+		for ( prefix in a ) {
+			buildParams( prefix, a[ prefix ], traditional, add );
+		}
+	}
+
+	// Return the resulting serialization
+	return s.join( "&" ).replace( r20, "+" );
+};
+
+function buildParams( prefix, obj, traditional, add ) {
+	var name;
+
+	if ( jQuery.isArray( obj ) ) {
+		// Serialize array item.
+		jQuery.each( obj, function( i, v ) {
+			if ( traditional || rbracket.test( prefix ) ) {
+				// Treat each array item as a scalar.
+				add( prefix, v );
+
+			} else {
+				// Item is non-scalar (array or object), encode its numeric index.
+				buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+			}
+		});
+
+	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
+		// Serialize object item.
+		for ( name in obj ) {
+			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+		}
+
+	} else {
+		// Serialize scalar item.
+		add( prefix, obj );
+	}
+}
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+	// Handle event binding
+	jQuery.fn[ name ] = function( data, fn ) {
+		return arguments.length > 0 ?
+			this.on( name, null, data, fn ) :
+			this.trigger( name );
+	};
+});
+
+jQuery.fn.extend({
+	hover: function( fnOver, fnOut ) {
+		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+	},
+
+	bind: function( types, data, fn ) {
+		return this.on( types, null, data, fn );
+	},
+	unbind: function( types, fn ) {
+		return this.off( types, null, fn );
+	},
+
+	delegate: function( selector, types, data, fn ) {
+		return this.on( types, selector, data, fn );
+	},
+	undelegate: function( selector, types, fn ) {
+		// ( namespace ) or ( selector, types [, fn] )
+		return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+	}
+});
+var
+	// Document location
+	ajaxLocParts,
+	ajaxLocation,
+	ajax_nonce = jQuery.now(),
+
+	ajax_rquery = /\?/,
+	rhash = /#.*$/,
+	rts = /([?&])_=[^&]*/,
+	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+	// #7653, #8125, #8152: local protocol detection
+	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
+	rnoContent = /^(?:GET|HEAD)$/,
+	rprotocol = /^\/\//,
+	rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
+
+	// Keep a copy of the old load method
+	_load = jQuery.fn.load,
+
+	/* Prefilters
+	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+	 * 2) These are called:
+	 *    - BEFORE asking for a transport
+	 *    - AFTER param serialization (s.data is a string if s.processData is true)
+	 * 3) key is the dataType
+	 * 4) the catchall symbol "*" can be used
+	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+	 */
+	prefilters = {},
+
+	/* Transports bindings
+	 * 1) key is the dataType
+	 * 2) the catchall symbol "*" can be used
+	 * 3) selection will start with transport dataType and THEN go to "*" if needed
+	 */
+	transports = {},
+
+	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+	allTypes = "*/".concat("*");
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+	ajaxLocation = location.href;
+} catch( e ) {
+	// Use the href attribute of an A element
+	// since IE will modify it given document.location
+	ajaxLocation = document.createElement( "a" );
+	ajaxLocation.href = "";
+	ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+	// dataTypeExpression is optional and defaults to "*"
+	return function( dataTypeExpression, func ) {
+
+		if ( typeof dataTypeExpression !== "string" ) {
+			func = dataTypeExpression;
+			dataTypeExpression = "*";
+		}
+
+		var dataType,
+			i = 0,
+			dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];
+
+		if ( jQuery.isFunction( func ) ) {
+			// For each dataType in the dataTypeExpression
+			while ( (dataType = dataTypes[i++]) ) {
+				// Prepend if requested
+				if ( dataType[0] === "+" ) {
+					dataType = dataType.slice( 1 ) || "*";
+					(structure[ dataType ] = structure[ dataType ] || []).unshift( func );
+
+				// Otherwise append
+				} else {
+					(structure[ dataType ] = structure[ dataType ] || []).push( func );
+				}
+			}
+		}
+	};
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
+
+	var inspected = {},
+		seekingTransport = ( structure === transports );
+
+	function inspect( dataType ) {
+		var selected;
+		inspected[ dataType ] = true;
+		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
+			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
+			if( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
+				options.dataTypes.unshift( dataTypeOrTransport );
+				inspect( dataTypeOrTransport );
+				return false;
+			} else if ( seekingTransport ) {
+				return !( selected = dataTypeOrTransport );
+			}
+		});
+		return selected;
+	}
+
+	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+	var deep, key,
+		flatOptions = jQuery.ajaxSettings.flatOptions || {};
+
+	for ( key in src ) {
+		if ( src[ key ] !== undefined ) {
+			( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
+		}
+	}
+	if ( deep ) {
+		jQuery.extend( true, target, deep );
+	}
+
+	return target;
+}
+
+jQuery.fn.load = function( url, params, callback ) {
+	if ( typeof url !== "string" && _load ) {
+		return _load.apply( this, arguments );
+	}
+
+	var selector, response, type,
+		self = this,
+		off = url.indexOf(" ");
+
+	if ( off >= 0 ) {
+		selector = url.slice( off, url.length );
+		url = url.slice( 0, off );
+	}
+
+	// If it's a function
+	if ( jQuery.isFunction( params ) ) {
+
+		// We assume that it's the callback
+		callback = params;
+		params = undefined;
+
+	// Otherwise, build a param string
+	} else if ( params && typeof params === "object" ) {
+		type = "POST";
+	}
+
+	// If we have elements to modify, make the request
+	if ( self.length > 0 ) {
+		jQuery.ajax({
+			url: url,
+
+			// if "type" variable is undefined, then "GET" method will be used
+			type: type,
+			dataType: "html",
+			data: params
+		}).done(function( responseText ) {
+
+			// Save response for use in complete callback
+			response = arguments;
+
+			self.html( selector ?
+
+				// If a selector was specified, locate the right elements in a dummy div
+				// Exclude scripts to avoid IE 'Permission Denied' errors
+				jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+				// Otherwise use the full result
+				responseText );
+
+		}).complete( callback && function( jqXHR, status ) {
+			self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+		});
+	}
+
+	return this;
+};
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){
+	jQuery.fn[ type ] = function( fn ){
+		return this.on( type, fn );
+	};
+});
+
+jQuery.extend({
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+	etag: {},
+
+	ajaxSettings: {
+		url: ajaxLocation,
+		type: "GET",
+		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+		global: true,
+		processData: true,
+		async: true,
+		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+		/*
+		timeout: 0,
+		data: null,
+		dataType: null,
+		username: null,
+		password: null,
+		cache: null,
+		throws: false,
+		traditional: false,
+		headers: {},
+		*/
+
+		accepts: {
+			"*": allTypes,
+			text: "text/plain",
+			html: "text/html",
+			xml: "application/xml, text/xml",
+			json: "application/json, text/javascript"
+		},
+
+		contents: {
+			xml: /xml/,
+			html: /html/,
+			json: /json/
+		},
+
+		responseFields: {
+			xml: "responseXML",
+			text: "responseText",
+			json: "responseJSON"
+		},
+
+		// Data converters
+		// Keys separate source (or catchall "*") and destination types with a single space
+		converters: {
+
+			// Convert anything to text
+			"* text": String,
+
+			// Text to html (true = no transformation)
+			"text html": true,
+
+			// Evaluate text as a json expression
+			"text json": jQuery.parseJSON,
+
+			// Parse text as xml
+			"text xml": jQuery.parseXML
+		},
+
+		// For options that shouldn't be deep extended:
+		// you can add your own custom options here if
+		// and when you create one that shouldn't be
+		// deep extended (see ajaxExtend)
+		flatOptions: {
+			url: true,
+			context: true
+		}
+	},
+
+	// Creates a full fledged settings object into target
+	// with both ajaxSettings and settings fields.
+	// If target is omitted, writes into ajaxSettings.
+	ajaxSetup: function( target, settings ) {
+		return settings ?
+
+			// Building a settings object
+			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+			// Extending ajaxSettings
+			ajaxExtend( jQuery.ajaxSettings, target );
+	},
+
+	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+	ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+	// Main method
+	ajax: function( url, options ) {
+
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
+			options = url;
+			url = undefined;
+		}
+
+		// Force options to be an object
+		options = options || {};
+
+		var // Cross-domain detection vars
+			parts,
+			// Loop variable
+			i,
+			// URL without anti-cache param
+			cacheURL,
+			// Response headers as string
+			responseHeadersString,
+			// timeout handle
+			timeoutTimer,
+
+			// To know if global events are to be dispatched
+			fireGlobals,
+
+			transport,
+			// Response headers
+			responseHeaders,
+			// Create the final options object
+			s = jQuery.ajaxSetup( {}, options ),
+			// Callbacks context
+			callbackContext = s.context || s,
+			// Context for global events is callbackContext if it is a DOM node or jQuery collection
+			globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
+				jQuery( callbackContext ) :
+				jQuery.event,
+			// Deferreds
+			deferred = jQuery.Deferred(),
+			completeDeferred = jQuery.Callbacks("once memory"),
+			// Status-dependent callbacks
+			statusCode = s.statusCode || {},
+			// Headers (they are sent all at once)
+			requestHeaders = {},
+			requestHeadersNames = {},
+			// The jqXHR state
+			state = 0,
+			// Default abort message
+			strAbort = "canceled",
+			// Fake xhr
+			jqXHR = {
+				readyState: 0,
+
+				// Builds headers hashtable if needed
+				getResponseHeader: function( key ) {
+					var match;
+					if ( state === 2 ) {
+						if ( !responseHeaders ) {
+							responseHeaders = {};
+							while ( (match = rheaders.exec( responseHeadersString )) ) {
+								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+							}
+						}
+						match = responseHeaders[ key.toLowerCase() ];
+					}
+					return match == null ? null : match;
+				},
+
+				// Raw string
+				getAllResponseHeaders: function() {
+					return state === 2 ? responseHeadersString : null;
+				},
+
+				// Caches the header
+				setRequestHeader: function( name, value ) {
+					var lname = name.toLowerCase();
+					if ( !state ) {
+						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+						requestHeaders[ name ] = value;
+					}
+					return this;
+				},
+
+				// Overrides response content-type header
+				overrideMimeType: function( type ) {
+					if ( !state ) {
+						s.mimeType = type;
+					}
+					return this;
+				},
+
+				// Status-dependent callbacks
+				statusCode: function( map ) {
+					var code;
+					if ( map ) {
+						if ( state < 2 ) {
+							for ( code in map ) {
+								// Lazy-add the new callback in a way that preserves old ones
+								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+							}
+						} else {
+							// Execute the appropriate callbacks
+							jqXHR.always( map[ jqXHR.status ] );
+						}
+					}
+					return this;
+				},
+
+				// Cancel the request
+				abort: function( statusText ) {
+					var finalText = statusText || strAbort;
+					if ( transport ) {
+						transport.abort( finalText );
+					}
+					done( 0, finalText );
+					return this;
+				}
+			};
+
+		// Attach deferreds
+		deferred.promise( jqXHR ).complete = completeDeferred.add;
+		jqXHR.success = jqXHR.done;
+		jqXHR.error = jqXHR.fail;
+
+		// Remove hash character (#7531: and string promotion)
+		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+		// Handle falsy url in the settings object (#10093: consistency with old signature)
+		// We also use the url parameter if available
+		s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+		// Alias method option to type as per ticket #12004
+		s.type = options.method || options.type || s.method || s.type;
+
+		// Extract dataTypes list
+		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];
+
+		// A cross-domain request is in order when we have a protocol:host:port mismatch
+		if ( s.crossDomain == null ) {
+			parts = rurl.exec( s.url.toLowerCase() );
+			s.crossDomain = !!( parts &&
+				( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
+					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
+						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
+			);
+		}
+
+		// Convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" ) {
+			s.data = jQuery.param( s.data, s.traditional );
+		}
+
+		// Apply prefilters
+		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+		// If request was aborted inside a prefilter, stop there
+		if ( state === 2 ) {
+			return jqXHR;
+		}
+
+		// We can fire global events as of now if asked to
+		fireGlobals = s.global;
+
+		// Watch for a new set of requests
+		if ( fireGlobals && jQuery.active++ === 0 ) {
+			jQuery.event.trigger("ajaxStart");
+		}
+
+		// Uppercase the type
+		s.type = s.type.toUpperCase();
+
+		// Determine if request has content
+		s.hasContent = !rnoContent.test( s.type );
+
+		// Save the URL in case we're toying with the If-Modified-Since
+		// and/or If-None-Match header later on
+		cacheURL = s.url;
+
+		// More options handling for requests with no content
+		if ( !s.hasContent ) {
+
+			// If data is available, append data to url
+			if ( s.data ) {
+				cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
+				// #9682: remove data so that it's not used in an eventual retry
+				delete s.data;
+			}
+
+			// Add anti-cache in url if needed
+			if ( s.cache === false ) {
+				s.url = rts.test( cacheURL ) ?
+
+					// If there is already a '_' parameter, set its value
+					cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
+
+					// Otherwise add one to the end
+					cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
+			}
+		}
+
+		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+		if ( s.ifModified ) {
+			if ( jQuery.lastModified[ cacheURL ] ) {
+				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+			}
+			if ( jQuery.etag[ cacheURL ] ) {
+				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+			}
+		}
+
+		// Set the correct header, if data is being sent
+		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+			jqXHR.setRequestHeader( "Content-Type", s.contentType );
+		}
+
+		// Set the Accepts header for the server, depending on the dataType
+		jqXHR.setRequestHeader(
+			"Accept",
+			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+				s.accepts[ "*" ]
+		);
+
+		// Check for headers option
+		for ( i in s.headers ) {
+			jqXHR.setRequestHeader( i, s.headers[ i ] );
+		}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+			// Abort if not done already and return
+			return jqXHR.abort();
+		}
+
+		// aborting is no longer a cancellation
+		strAbort = "abort";
+
+		// Install callbacks on deferreds
+		for ( i in { success: 1, error: 1, complete: 1 } ) {
+			jqXHR[ i ]( s[ i ] );
+		}
+
+		// Get transport
+		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+		// If no transport, we auto-abort
+		if ( !transport ) {
+			done( -1, "No Transport" );
+		} else {
+			jqXHR.readyState = 1;
+
+			// Send global event
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+			}
+			// Timeout
+			if ( s.async && s.timeout > 0 ) {
+				timeoutTimer = setTimeout(function() {
+					jqXHR.abort("timeout");
+				}, s.timeout );
+			}
+
+			try {
+				state = 1;
+				transport.send( requestHeaders, done );
+			} catch ( e ) {
+				// Propagate exception as error if not done
+				if ( state < 2 ) {
+					done( -1, e );
+				// Simply rethrow otherwise
+				} else {
+					throw e;
+				}
+			}
+		}
+
+		// Callback for when everything is done
+		function done( status, nativeStatusText, responses, headers ) {
+			var isSuccess, success, error, response, modified,
+				statusText = nativeStatusText;
+
+			// Called once
+			if ( state === 2 ) {
+				return;
+			}
+
+			// State is "done" now
+			state = 2;
+
+			// Clear timeout if it exists
+			if ( timeoutTimer ) {
+				clearTimeout( timeoutTimer );
+			}
+
+			// Dereference transport for early garbage collection
+			// (no matter how long the jqXHR object will be used)
+			transport = undefined;
+
+			// Cache response headers
+			responseHeadersString = headers || "";
+
+			// Set readyState
+			jqXHR.readyState = status > 0 ? 4 : 0;
+
+			// Determine if successful
+			isSuccess = status >= 200 && status < 300 || status === 304;
+
+			// Get response data
+			if ( responses ) {
+				response = ajaxHandleResponses( s, jqXHR, responses );
+			}
+
+			// Convert no matter what (that way responseXXX fields are always set)
+			response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+			// If successful, handle type chaining
+			if ( isSuccess ) {
+
+				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+				if ( s.ifModified ) {
+					modified = jqXHR.getResponseHeader("Last-Modified");
+					if ( modified ) {
+						jQuery.lastModified[ cacheURL ] = modified;
+					}
+					modified = jqXHR.getResponseHeader("etag");
+					if ( modified ) {
+						jQuery.etag[ cacheURL ] = modified;
+					}
+				}
+
+				// if no content
+				if ( status === 204 || s.type === "HEAD" ) {
+					statusText = "nocontent";
+
+				// if not modified
+				} else if ( status === 304 ) {
+					statusText = "notmodified";
+
+				// If we have data, let's convert it
+				} else {
+					statusText = response.state;
+					success = response.data;
+					error = response.error;
+					isSuccess = !error;
+				}
+			} else {
+				// We extract error from statusText
+				// then normalize statusText and status for non-aborts
+				error = statusText;
+				if ( status || !statusText ) {
+					statusText = "error";
+					if ( status < 0 ) {
+						status = 0;
+					}
+				}
+			}
+
+			// Set data for the fake xhr object
+			jqXHR.status = status;
+			jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+			// Success/Error
+			if ( isSuccess ) {
+				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+			} else {
+				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+			}
+
+			// Status-dependent callbacks
+			jqXHR.statusCode( statusCode );
+			statusCode = undefined;
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+					[ jqXHR, s, isSuccess ? success : error ] );
+			}
+
+			// Complete
+			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+				// Handle the global AJAX counter
+				if ( !( --jQuery.active ) ) {
+					jQuery.event.trigger("ajaxStop");
+				}
+			}
+		}
+
+		return jqXHR;
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get( url, data, callback, "json" );
+	},
+
+	getScript: function( url, callback ) {
+		return jQuery.get( url, undefined, callback, "script" );
+	}
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+	jQuery[ method ] = function( url, data, callback, type ) {
+		// shift arguments if data argument was omitted
+		if ( jQuery.isFunction( data ) ) {
+			type = type || callback;
+			callback = data;
+			data = undefined;
+		}
+
+		return jQuery.ajax({
+			url: url,
+			type: method,
+			dataType: type,
+			data: data,
+			success: callback
+		});
+	};
+});
+
+/* Handles responses to an ajax request:
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+	var firstDataType, ct, finalDataType, type,
+		contents = s.contents,
+		dataTypes = s.dataTypes;
+
+	// Remove auto dataType and get content-type in the process
+	while( dataTypes[ 0 ] === "*" ) {
+		dataTypes.shift();
+		if ( ct === undefined ) {
+			ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
+		}
+	}
+
+	// Check if we're dealing with a known content-type
+	if ( ct ) {
+		for ( type in contents ) {
+			if ( contents[ type ] && contents[ type ].test( ct ) ) {
+				dataTypes.unshift( type );
+				break;
+			}
+		}
+	}
+
+	// Check to see if we have a response for the expected dataType
+	if ( dataTypes[ 0 ] in responses ) {
+		finalDataType = dataTypes[ 0 ];
+	} else {
+		// Try convertible dataTypes
+		for ( type in responses ) {
+			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+				finalDataType = type;
+				break;
+			}
+			if ( !firstDataType ) {
+				firstDataType = type;
+			}
+		}
+		// Or just use first one
+		finalDataType = finalDataType || firstDataType;
+	}
+
+	// If we found a dataType
+	// We add the dataType to the list if needed
+	// and return the corresponding response
+	if ( finalDataType ) {
+		if ( finalDataType !== dataTypes[ 0 ] ) {
+			dataTypes.unshift( finalDataType );
+		}
+		return responses[ finalDataType ];
+	}
+}
+
+/* Chain conversions given the request and the original response
+ * Also sets the responseXXX fields on the jqXHR instance
+ */
+function ajaxConvert( s, response, jqXHR, isSuccess ) {
+	var conv2, current, conv, tmp, prev,
+		converters = {},
+		// Work with a copy of dataTypes in case we need to modify it for conversion
+		dataTypes = s.dataTypes.slice();
+
+	// Create converters map with lowercased keys
+	if ( dataTypes[ 1 ] ) {
+		for ( conv in s.converters ) {
+			converters[ conv.toLowerCase() ] = s.converters[ conv ];
+		}
+	}
+
+	current = dataTypes.shift();
+
+	// Convert to each sequential dataType
+	while ( current ) {
+
+		if ( s.responseFields[ current ] ) {
+			jqXHR[ s.responseFields[ current ] ] = response;
+		}
+
+		// Apply the dataFilter if provided
+		if ( !prev && isSuccess && s.dataFilter ) {
+			response = s.dataFilter( response, s.dataType );
+		}
+
+		prev = current;
+		current = dataTypes.shift();
+
+		if ( current ) {
+
+			// There's only work to do if current dataType is non-auto
+			if ( current === "*" ) {
+
+				current = prev;
+
+			// Convert response if prev dataType is non-auto and differs from current
+			} else if ( prev !== "*" && prev !== current ) {
+
+				// Seek a direct converter
+				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+				// If none found, seek a pair
+				if ( !conv ) {
+					for ( conv2 in converters ) {
+
+						// If conv2 outputs current
+						tmp = conv2.split( " " );
+						if ( tmp[ 1 ] === current ) {
+
+							// If prev can be converted to accepted input
+							conv = converters[ prev + " " + tmp[ 0 ] ] ||
+								converters[ "* " + tmp[ 0 ] ];
+							if ( conv ) {
+								// Condense equivalence converters
+								if ( conv === true ) {
+									conv = converters[ conv2 ];
+
+								// Otherwise, insert the intermediate dataType
+								} else if ( converters[ conv2 ] !== true ) {
+									current = tmp[ 0 ];
+									dataTypes.unshift( tmp[ 1 ] );
+								}
+								break;
+							}
+						}
+					}
+				}
+
+				// Apply converter (if not an equivalence)
+				if ( conv !== true ) {
+
+					// Unless errors are allowed to bubble, catch and return them
+					if ( conv && s[ "throws" ] ) {
+						response = conv( response );
+					} else {
+						try {
+							response = conv( response );
+						} catch ( e ) {
+							return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return { state: "success", data: response };
+}
+// Install script dataType
+jQuery.ajaxSetup({
+	accepts: {
+		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+	},
+	contents: {
+		script: /(?:java|ecma)script/
+	},
+	converters: {
+		"text script": function( text ) {
+			jQuery.globalEval( text );
+			return text;
+		}
+	}
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+	if ( s.cache === undefined ) {
+		s.cache = false;
+	}
+	if ( s.crossDomain ) {
+		s.type = "GET";
+		s.global = false;
+	}
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+	// This transport only deals with cross domain requests
+	if ( s.crossDomain ) {
+
+		var script,
+			head = document.head || jQuery("head")[0] || document.documentElement;
+
+		return {
+
+			send: function( _, callback ) {
+
+				script = document.createElement("script");
+
+				script.async = true;
+
+				if ( s.scriptCharset ) {
+					script.charset = s.scriptCharset;
+				}
+
+				script.src = s.url;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+						// Handle memory leak in IE
+						script.onload = script.onreadystatechange = null;
+
+						// Remove the script
+						if ( script.parentNode ) {
+							script.parentNode.removeChild( script );
+						}
+
+						// Dereference the script
+						script = null;
+
+						// Callback if not abort
+						if ( !isAbort ) {
+							callback( 200, "success" );
+						}
+					}
+				};
+
+				// Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
+				// Use native DOM manipulation to avoid our domManip AJAX trickery
+				head.insertBefore( script, head.firstChild );
+			},
+
+			abort: function() {
+				if ( script ) {
+					script.onload( undefined, true );
+				}
+			}
+		};
+	}
+});
+var oldCallbacks = [],
+	rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+	jsonp: "callback",
+	jsonpCallback: function() {
+		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
+		this[ callback ] = true;
+		return callback;
+	}
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+	var callbackName, overwritten, responseContainer,
+		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+			"url" :
+			typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
+		);
+
+	// Handle iff the expected data type is "jsonp" or we have a parameter to set
+	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
+
+		// Get callback name, remembering preexisting value associated with it
+		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+			s.jsonpCallback() :
+			s.jsonpCallback;
+
+		// Insert callback into url or form data
+		if ( jsonProp ) {
+			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+		} else if ( s.jsonp !== false ) {
+			s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+		}
+
+		// Use data converter to retrieve json after script execution
+		s.converters["script json"] = function() {
+			if ( !responseContainer ) {
+				jQuery.error( callbackName + " was not called" );
+			}
+			return responseContainer[ 0 ];
+		};
+
+		// force json dataType
+		s.dataTypes[ 0 ] = "json";
+
+		// Install callback
+		overwritten = window[ callbackName ];
+		window[ callbackName ] = function() {
+			responseContainer = arguments;
+		};
+
+		// Clean-up function (fires after converters)
+		jqXHR.always(function() {
+			// Restore preexisting value
+			window[ callbackName ] = overwritten;
+
+			// Save back as free
+			if ( s[ callbackName ] ) {
+				// make sure that re-using the options doesn't screw things around
+				s.jsonpCallback = originalSettings.jsonpCallback;
+
+				// save the callback name for future use
+				oldCallbacks.push( callbackName );
+			}
+
+			// Call if it was a function and we have a response
+			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+				overwritten( responseContainer[ 0 ] );
+			}
+
+			responseContainer = overwritten = undefined;
+		});
+
+		// Delegate to script
+		return "script";
+	}
+});
+var xhrCallbacks, xhrSupported,
+	xhrId = 0,
+	// #5280: Internet Explorer will keep connections alive if we don't abort on unload
+	xhrOnUnloadAbort = window.ActiveXObject && function() {
+		// Abort all pending requests
+		var key;
+		for ( key in xhrCallbacks ) {
+			xhrCallbacks[ key ]( undefined, true );
+		}
+	};
+
+// Functions to create xhrs
+function createStandardXHR() {
+	try {
+		return new window.XMLHttpRequest();
+	} catch( e ) {}
+}
+
+function createActiveXHR() {
+	try {
+		return new window.ActiveXObject("Microsoft.XMLHTTP");
+	} catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+	/* Microsoft failed to properly
+	 * implement the XMLHttpRequest in IE7 (can't request local files),
+	 * so we use the ActiveXObject when it is available
+	 * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+	 * we need a fallback.
+	 */
+	function() {
+		return !this.isLocal && createStandardXHR() || createActiveXHR();
+	} :
+	// For all other browsers, use the standard XMLHttpRequest object
+	createStandardXHR;
+
+// Determine support properties
+xhrSupported = jQuery.ajaxSettings.xhr();
+jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+xhrSupported = jQuery.support.ajax = !!xhrSupported;
+
+// Create transport if the browser can provide an xhr
+if ( xhrSupported ) {
+
+	jQuery.ajaxTransport(function( s ) {
+		// Cross domain only allowed if supported through XMLHttpRequest
+		if ( !s.crossDomain || jQuery.support.cors ) {
+
+			var callback;
+
+			return {
+				send: function( headers, complete ) {
+
+					// Get a new xhr
+					var handle, i,
+						xhr = s.xhr();
+
+					// Open the socket
+					// Passing null username, generates a login popup on Opera (#2865)
+					if ( s.username ) {
+						xhr.open( s.type, s.url, s.async, s.username, s.password );
+					} else {
+						xhr.open( s.type, s.url, s.async );
+					}
+
+					// Apply custom fields if provided
+					if ( s.xhrFields ) {
+						for ( i in s.xhrFields ) {
+							xhr[ i ] = s.xhrFields[ i ];
+						}
+					}
+
+					// Override mime type if needed
+					if ( s.mimeType && xhr.overrideMimeType ) {
+						xhr.overrideMimeType( s.mimeType );
+					}
+
+					// X-Requested-With header
+					// For cross-domain requests, seeing as conditions for a preflight are
+					// akin to a jigsaw puzzle, we simply never set it to be sure.
+					// (it can always be set on a per-request basis or even using ajaxSetup)
+					// For same-domain requests, won't change header if already provided.
+					if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+						headers["X-Requested-With"] = "XMLHttpRequest";
+					}
+
+					// Need an extra try/catch for cross domain requests in Firefox 3
+					try {
+						for ( i in headers ) {
+							xhr.setRequestHeader( i, headers[ i ] );
+						}
+					} catch( err ) {}
+
+					// Do send the request
+					// This may raise an exception which is actually
+					// handled in jQuery.ajax (so no try/catch here)
+					xhr.send( ( s.hasContent && s.data ) || null );
+
+					// Listener
+					callback = function( _, isAbort ) {
+						var status, responseHeaders, statusText, responses;
+
+						// Firefox throws exceptions when accessing properties
+						// of an xhr when a network error occurred
+						// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+						try {
+
+							// Was never called and is aborted or complete
+							if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+								// Only called once
+								callback = undefined;
+
+								// Do not keep as active anymore
+								if ( handle ) {
+									xhr.onreadystatechange = jQuery.noop;
+									if ( xhrOnUnloadAbort ) {
+										delete xhrCallbacks[ handle ];
+									}
+								}
+
+								// If it's an abort
+								if ( isAbort ) {
+									// Abort it manually if needed
+									if ( xhr.readyState !== 4 ) {
+										xhr.abort();
+									}
+								} else {
+									responses = {};
+									status = xhr.status;
+									responseHeaders = xhr.getAllResponseHeaders();
+
+									// When requesting binary data, IE6-9 will throw an exception
+									// on any attempt to access responseText (#11426)
+									if ( typeof xhr.responseText === "string" ) {
+										responses.text = xhr.responseText;
+									}
+
+									// Firefox throws an exception when accessing
+									// statusText for faulty cross-domain requests
+									try {
+										statusText = xhr.statusText;
+									} catch( e ) {
+										// We normalize with Webkit giving an empty statusText
+										statusText = "";
+									}
+
+									// Filter status for non standard behaviors
+
+									// If the request is local and we have data: assume a success
+									// (success with no data won't get notified, that's the best we
+									// can do given current implementations)
+									if ( !status && s.isLocal && !s.crossDomain ) {
+										status = responses.text ? 200 : 404;
+									// IE - #1450: sometimes returns 1223 when it should be 204
+									} else if ( status === 1223 ) {
+										status = 204;
+									}
+								}
+							}
+						} catch( firefoxAccessException ) {
+							if ( !isAbort ) {
+								complete( -1, firefoxAccessException );
+							}
+						}
+
+						// Call complete if needed
+						if ( responses ) {
+							complete( status, statusText, responses, responseHeaders );
+						}
+					};
+
+					if ( !s.async ) {
+						// if we're in sync mode we fire the callback
+						callback();
+					} else if ( xhr.readyState === 4 ) {
+						// (IE6 & IE7) if it's in cache and has been
+						// retrieved directly we need to fire the callback
+						setTimeout( callback );
+					} else {
+						handle = ++xhrId;
+						if ( xhrOnUnloadAbort ) {
+							// Create the active xhrs callbacks list if needed
+							// and attach the unload handler
+							if ( !xhrCallbacks ) {
+								xhrCallbacks = {};
+								jQuery( window ).unload( xhrOnUnloadAbort );
+							}
+							// Add to list of active xhrs callbacks
+							xhrCallbacks[ handle ] = callback;
+						}
+						xhr.onreadystatechange = callback;
+					}
+				},
+
+				abort: function() {
+					if ( callback ) {
+						callback( undefined, true );
+					}
+				}
+			};
+		}
+	});
+}
+var fxNow, timerId,
+	rfxtypes = /^(?:toggle|show|hide)$/,
+	rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
+	rrun = /queueHooks$/,
+	animationPrefilters = [ defaultPrefilter ],
+	tweeners = {
+		"*": [function( prop, value ) {
+			var tween = this.createTween( prop, value ),
+				target = tween.cur(),
+				parts = rfxnum.exec( value ),
+				unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+				// Starting value computation is required for potential unit mismatches
+				start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
+					rfxnum.exec( jQuery.css( tween.elem, prop ) ),
+				scale = 1,
+				maxIterations = 20;
+
+			if ( start && start[ 3 ] !== unit ) {
+				// Trust units reported by jQuery.css
+				unit = unit || start[ 3 ];
+
+				// Make sure we update the tween properties later on
+				parts = parts || [];
+
+				// Iteratively approximate from a nonzero starting point
+				start = +target || 1;
+
+				do {
+					// If previous iteration zeroed out, double until we get *something*
+					// Use a string for doubling factor so we don't accidentally see scale as unchanged below
+					scale = scale || ".5";
+
+					// Adjust and apply
+					start = start / scale;
+					jQuery.style( tween.elem, prop, start + unit );
+
+				// Update scale, tolerating zero or NaN from tween.cur()
+				// And breaking the loop if scale is unchanged or perfect, or if we've just had enough
+				} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
+			}
+
+			// Update tween properties
+			if ( parts ) {
+				start = tween.start = +start || +target || 0;
+				tween.unit = unit;
+				// If a +=/-= token was provided, we're doing a relative animation
+				tween.end = parts[ 1 ] ?
+					start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
+					+parts[ 2 ];
+			}
+
+			return tween;
+		}]
+	};
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+	setTimeout(function() {
+		fxNow = undefined;
+	});
+	return ( fxNow = jQuery.now() );
+}
+
+function createTween( value, prop, animation ) {
+	var tween,
+		collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+		index = 0,
+		length = collection.length;
+	for ( ; index < length; index++ ) {
+		if ( (tween = collection[ index ].call( animation, prop, value )) ) {
+
+			// we're done with this property
+			return tween;
+		}
+	}
+}
+
+function Animation( elem, properties, options ) {
+	var result,
+		stopped,
+		index = 0,
+		length = animationPrefilters.length,
+		deferred = jQuery.Deferred().always( function() {
+			// don't match elem in the :animated selector
+			delete tick.elem;
+		}),
+		tick = function() {
+			if ( stopped ) {
+				return false;
+			}
+			var currentTime = fxNow || createFxNow(),
+				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+				// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
+				temp = remaining / animation.duration || 0,
+				percent = 1 - temp,
+				index = 0,
+				length = animation.tweens.length;
+
+			for ( ; index < length ; index++ ) {
+				animation.tweens[ index ].run( percent );
+			}
+
+			deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+			if ( percent < 1 && length ) {
+				return remaining;
+			} else {
+				deferred.resolveWith( elem, [ animation ] );
+				return false;
+			}
+		},
+		animation = deferred.promise({
+			elem: elem,
+			props: jQuery.extend( {}, properties ),
+			opts: jQuery.extend( true, { specialEasing: {} }, options ),
+			originalProperties: properties,
+			originalOptions: options,
+			startTime: fxNow || createFxNow(),
+			duration: options.duration,
+			tweens: [],
+			createTween: function( prop, end ) {
+				var tween = jQuery.Tween( elem, animation.opts, prop, end,
+						animation.opts.specialEasing[ prop ] || animation.opts.easing );
+				animation.tweens.push( tween );
+				return tween;
+			},
+			stop: function( gotoEnd ) {
+				var index = 0,
+					// if we are going to the end, we want to run all the tweens
+					// otherwise we skip this part
+					length = gotoEnd ? animation.tweens.length : 0;
+				if ( stopped ) {
+					return this;
+				}
+				stopped = true;
+				for ( ; index < length ; index++ ) {
+					animation.tweens[ index ].run( 1 );
+				}
+
+				// resolve when we played the last frame
+				// otherwise, reject
+				if ( gotoEnd ) {
+					deferred.resolveWith( elem, [ animation, gotoEnd ] );
+				} else {
+					deferred.rejectWith( elem, [ animation, gotoEnd ] );
+				}
+				return this;
+			}
+		}),
+		props = animation.props;
+
+	propFilter( props, animation.opts.specialEasing );
+
+	for ( ; index < length ; index++ ) {
+		result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+		if ( result ) {
+			return result;
+		}
+	}
+
+	jQuery.map( props, createTween, animation );
+
+	if ( jQuery.isFunction( animation.opts.start ) ) {
+		animation.opts.start.call( elem, animation );
+	}
+
+	jQuery.fx.timer(
+		jQuery.extend( tick, {
+			elem: elem,
+			anim: animation,
+			queue: animation.opts.queue
+		})
+	);
+
+	// attach callbacks from options
+	return animation.progress( animation.opts.progress )
+		.done( animation.opts.done, animation.opts.complete )
+		.fail( animation.opts.fail )
+		.always( animation.opts.always );
+}
+
+function propFilter( props, specialEasing ) {
+	var index, name, easing, value, hooks;
+
+	// camelCase, specialEasing and expand cssHook pass
+	for ( index in props ) {
+		name = jQuery.camelCase( index );
+		easing = specialEasing[ name ];
+		value = props[ index ];
+		if ( jQuery.isArray( value ) ) {
+			easing = value[ 1 ];
+			value = props[ index ] = value[ 0 ];
+		}
+
+		if ( index !== name ) {
+			props[ name ] = value;
+			delete props[ index ];
+		}
+
+		hooks = jQuery.cssHooks[ name ];
+		if ( hooks && "expand" in hooks ) {
+			value = hooks.expand( value );
+			delete props[ name ];
+
+			// not quite $.extend, this wont overwrite keys already present.
+			// also - reusing 'index' from above because we have the correct "name"
+			for ( index in value ) {
+				if ( !( index in props ) ) {
+					props[ index ] = value[ index ];
+					specialEasing[ index ] = easing;
+				}
+			}
+		} else {
+			specialEasing[ name ] = easing;
+		}
+	}
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+	tweener: function( props, callback ) {
+		if ( jQuery.isFunction( props ) ) {
+			callback = props;
+			props = [ "*" ];
+		} else {
+			props = props.split(" ");
+		}
+
+		var prop,
+			index = 0,
+			length = props.length;
+
+		for ( ; index < length ; index++ ) {
+			prop = props[ index ];
+			tweeners[ prop ] = tweeners[ prop ] || [];
+			tweeners[ prop ].unshift( callback );
+		}
+	},
+
+	prefilter: function( callback, prepend ) {
+		if ( prepend ) {
+			animationPrefilters.unshift( callback );
+		} else {
+			animationPrefilters.push( callback );
+		}
+	}
+});
+
+function defaultPrefilter( elem, props, opts ) {
+	/* jshint validthis: true */
+	var prop, value, toggle, tween, hooks, oldfire,
+		anim = this,
+		orig = {},
+		style = elem.style,
+		hidden = elem.nodeType && isHidden( elem ),
+		dataShow = jQuery._data( elem, "fxshow" );
+
+	// handle queue: false promises
+	if ( !opts.queue ) {
+		hooks = jQuery._queueHooks( elem, "fx" );
+		if ( hooks.unqueued == null ) {
+			hooks.unqueued = 0;
+			oldfire = hooks.empty.fire;
+			hooks.empty.fire = function() {
+				if ( !hooks.unqueued ) {
+					oldfire();
+				}
+			};
+		}
+		hooks.unqueued++;
+
+		anim.always(function() {
+			// doing this makes sure that the complete handler will be called
+			// before this completes
+			anim.always(function() {
+				hooks.unqueued--;
+				if ( !jQuery.queue( elem, "fx" ).length ) {
+					hooks.empty.fire();
+				}
+			});
+		});
+	}
+
+	// height/width overflow pass
+	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+		// Make sure that nothing sneaks out
+		// Record all 3 overflow attributes because IE does not
+		// change the overflow attribute when overflowX and
+		// overflowY are set to the same value
+		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+		// Set display property to inline-block for height/width
+		// animations on inline elements that are having width/height animated
+		if ( jQuery.css( elem, "display" ) === "inline" &&
+				jQuery.css( elem, "float" ) === "none" ) {
+
+			// inline-level elements accept inline-block;
+			// block-level elements need to be inline with layout
+			if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
+				style.display = "inline-block";
+
+			} else {
+				style.zoom = 1;
+			}
+		}
+	}
+
+	if ( opts.overflow ) {
+		style.overflow = "hidden";
+		if ( !jQuery.support.shrinkWrapBlocks ) {
+			anim.always(function() {
+				style.overflow = opts.overflow[ 0 ];
+				style.overflowX = opts.overflow[ 1 ];
+				style.overflowY = opts.overflow[ 2 ];
+			});
+		}
+	}
+
+
+	// show/hide pass
+	for ( prop in props ) {
+		value = props[ prop ];
+		if ( rfxtypes.exec( value ) ) {
+			delete props[ prop ];
+			toggle = toggle || value === "toggle";
+			if ( value === ( hidden ? "hide" : "show" ) ) {
+				continue;
+			}
+			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+		}
+	}
+
+	if ( !jQuery.isEmptyObject( orig ) ) {
+		if ( dataShow ) {
+			if ( "hidden" in dataShow ) {
+				hidden = dataShow.hidden;
+			}
+		} else {
+			dataShow = jQuery._data( elem, "fxshow", {} );
+		}
+
+		// store state if its toggle - enables .stop().toggle() to "reverse"
+		if ( toggle ) {
+			dataShow.hidden = !hidden;
+		}
+		if ( hidden ) {
+			jQuery( elem ).show();
+		} else {
+			anim.done(function() {
+				jQuery( elem ).hide();
+			});
+		}
+		anim.done(function() {
+			var prop;
+			jQuery._removeData( elem, "fxshow" );
+			for ( prop in orig ) {
+				jQuery.style( elem, prop, orig[ prop ] );
+			}
+		});
+		for ( prop in orig ) {
+			tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
+
+			if ( !( prop in dataShow ) ) {
+				dataShow[ prop ] = tween.start;
+				if ( hidden ) {
+					tween.end = tween.start;
+					tween.start = prop === "width" || prop === "height" ? 1 : 0;
+				}
+			}
+		}
+	}
+}
+
+function Tween( elem, options, prop, end, easing ) {
+	return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+	constructor: Tween,
+	init: function( elem, options, prop, end, easing, unit ) {
+		this.elem = elem;
+		this.prop = prop;
+		this.easing = easing || "swing";
+		this.options = options;
+		this.start = this.now = this.cur();
+		this.end = end;
+		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+	},
+	cur: function() {
+		var hooks = Tween.propHooks[ this.prop ];
+
+		return hooks && hooks.get ?
+			hooks.get( this ) :
+			Tween.propHooks._default.get( this );
+	},
+	run: function( percent ) {
+		var eased,
+			hooks = Tween.propHooks[ this.prop ];
+
+		if ( this.options.duration ) {
+			this.pos = eased = jQuery.easing[ this.easing ](
+				percent, this.options.duration * percent, 0, 1, this.options.duration
+			);
+		} else {
+			this.pos = eased = percent;
+		}
+		this.now = ( this.end - this.start ) * eased + this.start;
+
+		if ( this.options.step ) {
+			this.options.step.call( this.elem, this.now, this );
+		}
+
+		if ( hooks && hooks.set ) {
+			hooks.set( this );
+		} else {
+			Tween.propHooks._default.set( this );
+		}
+		return this;
+	}
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+	_default: {
+		get: function( tween ) {
+			var result;
+
+			if ( tween.elem[ tween.prop ] != null &&
+				(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+				return tween.elem[ tween.prop ];
+			}
+
+			// passing an empty string as a 3rd parameter to .css will automatically
+			// attempt a parseFloat and fallback to a string if the parse fails
+			// so, simple values such as "10px" are parsed to Float.
+			// complex values such as "rotate(1rad)" are returned as is.
+			result = jQuery.css( tween.elem, tween.prop, "" );
+			// Empty strings, null, undefined and "auto" are converted to 0.
+			return !result || result === "auto" ? 0 : result;
+		},
+		set: function( tween ) {
+			// use step hook for back compat - use cssHook if its there - use .style if its
+			// available and use plain properties where available
+			if ( jQuery.fx.step[ tween.prop ] ) {
+				jQuery.fx.step[ tween.prop ]( tween );
+			} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+			} else {
+				tween.elem[ tween.prop ] = tween.now;
+			}
+		}
+	}
+};
+
+// Support: IE <=9
+// Panic based approach to setting things on disconnected nodes
+
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+	set: function( tween ) {
+		if ( tween.elem.nodeType && tween.elem.parentNode ) {
+			tween.elem[ tween.prop ] = tween.now;
+		}
+	}
+};
+
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+	var cssFn = jQuery.fn[ name ];
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return speed == null || typeof speed === "boolean" ?
+			cssFn.apply( this, arguments ) :
+			this.animate( genFx( name, true ), speed, easing, callback );
+	};
+});
+
+jQuery.fn.extend({
+	fadeTo: function( speed, to, easing, callback ) {
+
+		// show any hidden elements after setting opacity to 0
+		return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+			// animate to the value specified
+			.end().animate({ opacity: to }, speed, easing, callback );
+	},
+	animate: function( prop, speed, easing, callback ) {
+		var empty = jQuery.isEmptyObject( prop ),
+			optall = jQuery.speed( speed, easing, callback ),
+			doAnimation = function() {
+				// Operate on a copy of prop so per-property easing won't be lost
+				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+				// Empty animations, or finishing resolves immediately
+				if ( empty || jQuery._data( this, "finish" ) ) {
+					anim.stop( true );
+				}
+			};
+			doAnimation.finish = doAnimation;
+
+		return empty || optall.queue === false ?
+			this.each( doAnimation ) :
+			this.queue( optall.queue, doAnimation );
+	},
+	stop: function( type, clearQueue, gotoEnd ) {
+		var stopQueue = function( hooks ) {
+			var stop = hooks.stop;
+			delete hooks.stop;
+			stop( gotoEnd );
+		};
+
+		if ( typeof type !== "string" ) {
+			gotoEnd = clearQueue;
+			clearQueue = type;
+			type = undefined;
+		}
+		if ( clearQueue && type !== false ) {
+			this.queue( type || "fx", [] );
+		}
+
+		return this.each(function() {
+			var dequeue = true,
+				index = type != null && type + "queueHooks",
+				timers = jQuery.timers,
+				data = jQuery._data( this );
+
+			if ( index ) {
+				if ( data[ index ] && data[ index ].stop ) {
+					stopQueue( data[ index ] );
+				}
+			} else {
+				for ( index in data ) {
+					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+						stopQueue( data[ index ] );
+					}
+				}
+			}
+
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+					timers[ index ].anim.stop( gotoEnd );
+					dequeue = false;
+					timers.splice( index, 1 );
+				}
+			}
+
+			// start the next in the queue if the last step wasn't forced
+			// timers currently will call their complete callbacks, which will dequeue
+			// but only if they were gotoEnd
+			if ( dequeue || !gotoEnd ) {
+				jQuery.dequeue( this, type );
+			}
+		});
+	},
+	finish: function( type ) {
+		if ( type !== false ) {
+			type = type || "fx";
+		}
+		return this.each(function() {
+			var index,
+				data = jQuery._data( this ),
+				queue = data[ type + "queue" ],
+				hooks = data[ type + "queueHooks" ],
+				timers = jQuery.timers,
+				length = queue ? queue.length : 0;
+
+			// enable finishing flag on private data
+			data.finish = true;
+
+			// empty the queue first
+			jQuery.queue( this, type, [] );
+
+			if ( hooks && hooks.stop ) {
+				hooks.stop.call( this, true );
+			}
+
+			// look for any active animations, and finish them
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+					timers[ index ].anim.stop( true );
+					timers.splice( index, 1 );
+				}
+			}
+
+			// look for any animations in the old queue and finish them
+			for ( index = 0; index < length; index++ ) {
+				if ( queue[ index ] && queue[ index ].finish ) {
+					queue[ index ].finish.call( this );
+				}
+			}
+
+			// turn off finishing flag
+			delete data.finish;
+		});
+	}
+});
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+	var which,
+		attrs = { height: type },
+		i = 0;
+
+	// if we include width, step value is 1 to do all cssExpand values,
+	// if we don't include width, step value is 2 to skip over Left and Right
+	includeWidth = includeWidth? 1 : 0;
+	for( ; i < 4 ; i += 2 - includeWidth ) {
+		which = cssExpand[ i ];
+		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+	}
+
+	if ( includeWidth ) {
+		attrs.opacity = attrs.width = type;
+	}
+
+	return attrs;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+	slideDown: genFx("show"),
+	slideUp: genFx("hide"),
+	slideToggle: genFx("toggle"),
+	fadeIn: { opacity: "show" },
+	fadeOut: { opacity: "hide" },
+	fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return this.animate( props, speed, easing, callback );
+	};
+});
+
+jQuery.speed = function( speed, easing, fn ) {
+	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+		complete: fn || !fn && easing ||
+			jQuery.isFunction( speed ) && speed,
+		duration: speed,
+		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+	};
+
+	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+		opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+	// normalize opt.queue - true/undefined/null -> "fx"
+	if ( opt.queue == null || opt.queue === true ) {
+		opt.queue = "fx";
+	}
+
+	// Queueing
+	opt.old = opt.complete;
+
+	opt.complete = function() {
+		if ( jQuery.isFunction( opt.old ) ) {
+			opt.old.call( this );
+		}
+
+		if ( opt.queue ) {
+			jQuery.dequeue( this, opt.queue );
+		}
+	};
+
+	return opt;
+};
+
+jQuery.easing = {
+	linear: function( p ) {
+		return p;
+	},
+	swing: function( p ) {
+		return 0.5 - Math.cos( p*Math.PI ) / 2;
+	}
+};
+
+jQuery.timers = [];
+jQuery.fx = Tween.prototype.init;
+jQuery.fx.tick = function() {
+	var timer,
+		timers = jQuery.timers,
+		i = 0;
+
+	fxNow = jQuery.now();
+
+	for ( ; i < timers.length; i++ ) {
+		timer = timers[ i ];
+		// Checks the timer has not already been removed
+		if ( !timer() && timers[ i ] === timer ) {
+			timers.splice( i--, 1 );
+		}
+	}
+
+	if ( !timers.length ) {
+		jQuery.fx.stop();
+	}
+	fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+	if ( timer() && jQuery.timers.push( timer ) ) {
+		jQuery.fx.start();
+	}
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.start = function() {
+	if ( !timerId ) {
+		timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+	}
+};
+
+jQuery.fx.stop = function() {
+	clearInterval( timerId );
+	timerId = null;
+};
+
+jQuery.fx.speeds = {
+	slow: 600,
+	fast: 200,
+	// Default speed
+	_default: 400
+};
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.animated = function( elem ) {
+		return jQuery.grep(jQuery.timers, function( fn ) {
+			return elem === fn.elem;
+		}).length;
+	};
+}
+jQuery.fn.offset = function( options ) {
+	if ( arguments.length ) {
+		return options === undefined ?
+			this :
+			this.each(function( i ) {
+				jQuery.offset.setOffset( this, options, i );
+			});
+	}
+
+	var docElem, win,
+		box = { top: 0, left: 0 },
+		elem = this[ 0 ],
+		doc = elem && elem.ownerDocument;
+
+	if ( !doc ) {
+		return;
+	}
+
+	docElem = doc.documentElement;
+
+	// Make sure it's not a disconnected DOM node
+	if ( !jQuery.contains( docElem, elem ) ) {
+		return box;
+	}
+
+	// If we don't have gBCR, just use 0,0 rather than error
+	// BlackBerry 5, iOS 3 (original iPhone)
+	if ( typeof elem.getBoundingClientRect !== core_strundefined ) {
+		box = elem.getBoundingClientRect();
+	}
+	win = getWindow( doc );
+	return {
+		top: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0 ),
+		left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
+	};
+};
+
+jQuery.offset = {
+
+	setOffset: function( elem, options, i ) {
+		var position = jQuery.css( elem, "position" );
+
+		// set position first, in-case top/left are set even on static elem
+		if ( position === "static" ) {
+			elem.style.position = "relative";
+		}
+
+		var curElem = jQuery( elem ),
+			curOffset = curElem.offset(),
+			curCSSTop = jQuery.css( elem, "top" ),
+			curCSSLeft = jQuery.css( elem, "left" ),
+			calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+			props = {}, curPosition = {}, curTop, curLeft;
+
+		// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+		if ( calculatePosition ) {
+			curPosition = curElem.position();
+			curTop = curPosition.top;
+			curLeft = curPosition.left;
+		} else {
+			curTop = parseFloat( curCSSTop ) || 0;
+			curLeft = parseFloat( curCSSLeft ) || 0;
+		}
+
+		if ( jQuery.isFunction( options ) ) {
+			options = options.call( elem, i, curOffset );
+		}
+
+		if ( options.top != null ) {
+			props.top = ( options.top - curOffset.top ) + curTop;
+		}
+		if ( options.left != null ) {
+			props.left = ( options.left - curOffset.left ) + curLeft;
+		}
+
+		if ( "using" in options ) {
+			options.using.call( elem, props );
+		} else {
+			curElem.css( props );
+		}
+	}
+};
+
+
+jQuery.fn.extend({
+
+	position: function() {
+		if ( !this[ 0 ] ) {
+			return;
+		}
+
+		var offsetParent, offset,
+			parentOffset = { top: 0, left: 0 },
+			elem = this[ 0 ];
+
+		// fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
+		if ( jQuery.css( elem, "position" ) === "fixed" ) {
+			// we assume that getBoundingClientRect is available when computed position is fixed
+			offset = elem.getBoundingClientRect();
+		} else {
+			// Get *real* offsetParent
+			offsetParent = this.offsetParent();
+
+			// Get correct offsets
+			offset = this.offset();
+			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+				parentOffset = offsetParent.offset();
+			}
+
+			// Add offsetParent borders
+			parentOffset.top  += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
+			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
+		}
+
+		// Subtract parent offsets and element margins
+		// note: when an element has margin: auto the offsetLeft and marginLeft
+		// are the same in Safari causing offset.left to incorrectly be 0
+		return {
+			top:  offset.top  - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true)
+		};
+	},
+
+	offsetParent: function() {
+		return this.map(function() {
+			var offsetParent = this.offsetParent || docElem;
+			while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) {
+				offsetParent = offsetParent.offsetParent;
+			}
+			return offsetParent || docElem;
+		});
+	}
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+	var top = /Y/.test( prop );
+
+	jQuery.fn[ method ] = function( val ) {
+		return jQuery.access( this, function( elem, method, val ) {
+			var win = getWindow( elem );
+
+			if ( val === undefined ) {
+				return win ? (prop in win) ? win[ prop ] :
+					win.document.documentElement[ method ] :
+					elem[ method ];
+			}
+
+			if ( win ) {
+				win.scrollTo(
+					!top ? val : jQuery( win ).scrollLeft(),
+					top ? val : jQuery( win ).scrollTop()
+				);
+
+			} else {
+				elem[ method ] = val;
+			}
+		}, method, val, arguments.length, null );
+	};
+});
+
+function getWindow( elem ) {
+	return jQuery.isWindow( elem ) ?
+		elem :
+		elem.nodeType === 9 ?
+			elem.defaultView || elem.parentWindow :
+			false;
+}
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+		// margin is only for outerHeight, outerWidth
+		jQuery.fn[ funcName ] = function( margin, value ) {
+			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+			return jQuery.access( this, function( elem, type, value ) {
+				var doc;
+
+				if ( jQuery.isWindow( elem ) ) {
+					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+					// isn't a whole lot we can do. See pull request at this URL for discussion:
+					// https://github.com/jquery/jquery/pull/764
+					return elem.document.documentElement[ "client" + name ];
+				}
+
+				// Get document width or height
+				if ( elem.nodeType === 9 ) {
+					doc = elem.documentElement;
+
+					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
+					// unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
+					return Math.max(
+						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+						elem.body[ "offset" + name ], doc[ "offset" + name ],
+						doc[ "client" + name ]
+					);
+				}
+
+				return value === undefined ?
+					// Get width or height on the element, requesting but not forcing parseFloat
+					jQuery.css( elem, type, extra ) :
+
+					// Set width or height on the element
+					jQuery.style( elem, type, value, extra );
+			}, type, chainable ? margin : undefined, chainable, null );
+		};
+	});
+});
+// Limit scope pollution from any deprecated API
+// (function() {
+
+// The number of elements contained in the matched element set
+jQuery.fn.size = function() {
+	return this.length;
+};
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+// })();
+if ( typeof module === "object" && module && typeof module.exports === "object" ) {
+	// Expose jQuery as module.exports in loaders that implement the Node
+	// module pattern (including browserify). Do not create the global, since
+	// the user will be storing it themselves locally, and globals are frowned
+	// upon in the Node module world.
+	module.exports = jQuery;
+} else {
+	// Otherwise expose jQuery to the global object as usual
+	window.jQuery = window.$ = jQuery;
+
+	// Register as a named AMD module, since jQuery can be concatenated with other
+	// files that may use define, but not via a proper concatenation script that
+	// understands anonymous AMD modules. A named AMD is safest and most robust
+	// way to register. Lowercase jquery is used because AMD module names are
+	// derived from file names, and jQuery is normally delivered in a lowercase
+	// file name. Do this after creating the global so that if an AMD module wants
+	// to call noConflict to hide this version of jQuery, it will work.
+	if ( typeof define === "function" && define.amd ) {
+		define( "jquery", [], function () { return jQuery; } );
+	}
+}
+
+})( window );
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/index.html
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/index.html	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/index.html	(revision 2119)
@@ -0,0 +1,513 @@
+<!doctype html>
+<html lang="us">
+<head>
+	<meta charset="utf-8">
+	<title>jQuery UI Example Page</title>
+	<link href="jquery-ui.css" rel="stylesheet">
+	<style>
+	body{
+		font: 62.5% "Trebuchet MS", sans-serif;
+		margin: 50px;
+	}
+	.demoHeaders {
+		margin-top: 2em;
+	}
+	#dialog-link {
+		padding: .4em 1em .4em 20px;
+		text-decoration: none;
+		position: relative;
+	}
+	#dialog-link span.ui-icon {
+		margin: 0 5px 0 0;
+		position: absolute;
+		left: .2em;
+		top: 50%;
+		margin-top: -8px;
+	}
+	#icons {
+		margin: 0;
+		padding: 0;
+	}
+	#icons li {
+		margin: 2px;
+		position: relative;
+		padding: 4px 0;
+		cursor: pointer;
+		float: left;
+		list-style: none;
+	}
+	#icons span.ui-icon {
+		float: left;
+		margin: 0 4px;
+	}
+	.fakewindowcontain .ui-widget-overlay {
+		position: absolute;
+	}
+	select {
+		width: 200px;
+	}
+	</style>
+</head>
+<body>
+
+<h1>Welcome to jQuery UI!</h1>
+
+<div class="ui-widget">
+	<p>This page demonstrates the widgets and theme you selected in Download Builder. Please make sure you are using them with a compatible jQuery version.</p>
+</div>
+
+<h1>YOUR COMPONENTS:</h1>
+
+
+<!-- Accordion -->
+<h2 class="demoHeaders">Accordion</h2>
+<div id="accordion">
+	<h3>First</h3>
+	<div>Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet.</div>
+	<h3>Second</h3>
+	<div>Phasellus mattis tincidunt nibh.</div>
+	<h3>Third</h3>
+	<div>Nam dui erat, auctor a, dignissim quis.</div>
+</div>
+
+
+
+<!-- Autocomplete -->
+<h2 class="demoHeaders">Autocomplete</h2>
+<div>
+	<input id="autocomplete" title="type &quot;a&quot;">
+</div>
+
+
+
+<!-- Button -->
+<h2 class="demoHeaders">Button</h2>
+<button id="button">A button element</button>
+<form style="margin-top: 1em;">
+	<div id="radioset">
+		<input type="radio" id="radio1" name="radio"><label for="radio1">Choice 1</label>
+		<input type="radio" id="radio2" name="radio" checked="checked"><label for="radio2">Choice 2</label>
+		<input type="radio" id="radio3" name="radio"><label for="radio3">Choice 3</label>
+	</div>
+</form>
+
+
+
+<!-- Tabs -->
+<h2 class="demoHeaders">Tabs</h2>
+<div id="tabs">
+	<ul>
+		<li><a href="#tabs-1">First</a></li>
+		<li><a href="#tabs-2">Second</a></li>
+		<li><a href="#tabs-3">Third</a></li>
+	</ul>
+	<div id="tabs-1">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</div>
+	<div id="tabs-2">Phasellus mattis tincidunt nibh. Cras orci urna, blandit id, pretium vel, aliquet ornare, felis. Maecenas scelerisque sem non nisl. Fusce sed lorem in enim dictum bibendum.</div>
+	<div id="tabs-3">Nam dui erat, auctor a, dignissim quis, sollicitudin eu, felis. Pellentesque nisi urna, interdum eget, sagittis et, consequat vestibulum, lacus. Mauris porttitor ullamcorper augue.</div>
+</div>
+
+
+
+<!-- Dialog NOTE: Dialog is not generated by UI in this demo so it can be visually styled in themeroller-->
+<h2 class="demoHeaders">Dialog</h2>
+<p><a href="#" id="dialog-link" class="ui-state-default ui-corner-all"><span class="ui-icon ui-icon-newwin"></span>Open Dialog</a></p>
+
+<h2 class="demoHeaders">Overlay and Shadow Classes <em>(not currently used in UI widgets)</em></h2>
+<div style="position: relative; width: 96%; height: 200px; padding:1% 2%; overflow:hidden;" class="fakewindowcontain">
+	<p>Lorem ipsum dolor sit amet,  Nulla nec tortor. Donec id elit quis purus consectetur consequat. </p><p>Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante. Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci. </p><p>Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam. Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat. </p><p>Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante. Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci. Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam. </p><p>Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat. Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante. </p><p>Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci. Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam. Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat. Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. </p>
+
+	<!-- ui-dialog -->
+	<div class="ui-overlay"><div class="ui-widget-overlay"></div><div class="ui-widget-shadow ui-corner-all" style="width: 302px; height: 152px; position: absolute; left: 50px; top: 30px;"></div></div>
+	<div style="position: absolute; width: 280px; height: 130px;left: 50px; top: 30px; padding: 10px;" class="ui-widget ui-widget-content ui-corner-all">
+		<div class="ui-dialog-content ui-widget-content" style="background: none; border: 0;">
+			<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
+		</div>
+	</div>
+
+</div>
+
+<!-- ui-dialog -->
+<div id="dialog" title="Dialog Title">
+	<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
+</div>
+
+
+
+<h2 class="demoHeaders">Framework Icons (content color preview)</h2>
+<ul id="icons" class="ui-widget ui-helper-clearfix">
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-carat-1-n"><span class="ui-icon ui-icon-carat-1-n"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-carat-1-ne"><span class="ui-icon ui-icon-carat-1-ne"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-carat-1-e"><span class="ui-icon ui-icon-carat-1-e"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-carat-1-se"><span class="ui-icon ui-icon-carat-1-se"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-carat-1-s"><span class="ui-icon ui-icon-carat-1-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-carat-1-sw"><span class="ui-icon ui-icon-carat-1-sw"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-carat-1-w"><span class="ui-icon ui-icon-carat-1-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-carat-1-nw"><span class="ui-icon ui-icon-carat-1-nw"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-carat-2-n-s"><span class="ui-icon ui-icon-carat-2-n-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-carat-2-e-w"><span class="ui-icon ui-icon-carat-2-e-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-n"><span class="ui-icon ui-icon-triangle-1-n"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-ne"><span class="ui-icon ui-icon-triangle-1-ne"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-e"><span class="ui-icon ui-icon-triangle-1-e"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-se"><span class="ui-icon ui-icon-triangle-1-se"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-s"><span class="ui-icon ui-icon-triangle-1-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-sw"><span class="ui-icon ui-icon-triangle-1-sw"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-w"><span class="ui-icon ui-icon-triangle-1-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-1-nw"><span class="ui-icon ui-icon-triangle-1-nw"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-2-n-s"><span class="ui-icon ui-icon-triangle-2-n-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-triangle-2-e-w"><span class="ui-icon ui-icon-triangle-2-e-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-n"><span class="ui-icon ui-icon-arrow-1-n"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-ne"><span class="ui-icon ui-icon-arrow-1-ne"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-e"><span class="ui-icon ui-icon-arrow-1-e"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-se"><span class="ui-icon ui-icon-arrow-1-se"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-s"><span class="ui-icon ui-icon-arrow-1-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-sw"><span class="ui-icon ui-icon-arrow-1-sw"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-w"><span class="ui-icon ui-icon-arrow-1-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-1-nw"><span class="ui-icon ui-icon-arrow-1-nw"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-2-n-s"><span class="ui-icon ui-icon-arrow-2-n-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-2-ne-sw"><span class="ui-icon ui-icon-arrow-2-ne-sw"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-2-e-w"><span class="ui-icon ui-icon-arrow-2-e-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-2-se-nw"><span class="ui-icon ui-icon-arrow-2-se-nw"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowstop-1-n"><span class="ui-icon ui-icon-arrowstop-1-n"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowstop-1-e"><span class="ui-icon ui-icon-arrowstop-1-e"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowstop-1-s"><span class="ui-icon ui-icon-arrowstop-1-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowstop-1-w"><span class="ui-icon ui-icon-arrowstop-1-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-n"><span class="ui-icon ui-icon-arrowthick-1-n"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-ne"><span class="ui-icon ui-icon-arrowthick-1-ne"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-e"><span class="ui-icon ui-icon-arrowthick-1-e"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-se"><span class="ui-icon ui-icon-arrowthick-1-se"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-s"><span class="ui-icon ui-icon-arrowthick-1-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-sw"><span class="ui-icon ui-icon-arrowthick-1-sw"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-w"><span class="ui-icon ui-icon-arrowthick-1-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-1-nw"><span class="ui-icon ui-icon-arrowthick-1-nw"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-2-n-s"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-2-ne-sw"><span class="ui-icon ui-icon-arrowthick-2-ne-sw"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-2-e-w"><span class="ui-icon ui-icon-arrowthick-2-e-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthick-2-se-nw"><span class="ui-icon ui-icon-arrowthick-2-se-nw"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthickstop-1-n"><span class="ui-icon ui-icon-arrowthickstop-1-n"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthickstop-1-e"><span class="ui-icon ui-icon-arrowthickstop-1-e"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthickstop-1-s"><span class="ui-icon ui-icon-arrowthickstop-1-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowthickstop-1-w"><span class="ui-icon ui-icon-arrowthickstop-1-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturnthick-1-w"><span class="ui-icon ui-icon-arrowreturnthick-1-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturnthick-1-n"><span class="ui-icon ui-icon-arrowreturnthick-1-n"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturnthick-1-e"><span class="ui-icon ui-icon-arrowreturnthick-1-e"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturnthick-1-s"><span class="ui-icon ui-icon-arrowreturnthick-1-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturn-1-w"><span class="ui-icon ui-icon-arrowreturn-1-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturn-1-n"><span class="ui-icon ui-icon-arrowreturn-1-n"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturn-1-e"><span class="ui-icon ui-icon-arrowreturn-1-e"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowreturn-1-s"><span class="ui-icon ui-icon-arrowreturn-1-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowrefresh-1-w"><span class="ui-icon ui-icon-arrowrefresh-1-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowrefresh-1-n"><span class="ui-icon ui-icon-arrowrefresh-1-n"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowrefresh-1-e"><span class="ui-icon ui-icon-arrowrefresh-1-e"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrowrefresh-1-s"><span class="ui-icon ui-icon-arrowrefresh-1-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-4"><span class="ui-icon ui-icon-arrow-4"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-arrow-4-diag"><span class="ui-icon ui-icon-arrow-4-diag"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-extlink"><span class="ui-icon ui-icon-extlink"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-newwin"><span class="ui-icon ui-icon-newwin"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-refresh"><span class="ui-icon ui-icon-refresh"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-shuffle"><span class="ui-icon ui-icon-shuffle"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-transfer-e-w"><span class="ui-icon ui-icon-transfer-e-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-transferthick-e-w"><span class="ui-icon ui-icon-transferthick-e-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-folder-collapsed"><span class="ui-icon ui-icon-folder-collapsed"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-folder-open"><span class="ui-icon ui-icon-folder-open"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-document"><span class="ui-icon ui-icon-document"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-document-b"><span class="ui-icon ui-icon-document-b"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-note"><span class="ui-icon ui-icon-note"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-mail-closed"><span class="ui-icon ui-icon-mail-closed"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-mail-open"><span class="ui-icon ui-icon-mail-open"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-suitcase"><span class="ui-icon ui-icon-suitcase"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-comment"><span class="ui-icon ui-icon-comment"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-person"><span class="ui-icon ui-icon-person"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-print"><span class="ui-icon ui-icon-print"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-trash"><span class="ui-icon ui-icon-trash"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-locked"><span class="ui-icon ui-icon-locked"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-unlocked"><span class="ui-icon ui-icon-unlocked"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-bookmark"><span class="ui-icon ui-icon-bookmark"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-tag"><span class="ui-icon ui-icon-tag"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-home"><span class="ui-icon ui-icon-home"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-flag"><span class="ui-icon ui-icon-flag"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-calculator"><span class="ui-icon ui-icon-calculator"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-cart"><span class="ui-icon ui-icon-cart"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-pencil"><span class="ui-icon ui-icon-pencil"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-clock"><span class="ui-icon ui-icon-clock"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-disk"><span class="ui-icon ui-icon-disk"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-calendar"><span class="ui-icon ui-icon-calendar"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-zoomin"><span class="ui-icon ui-icon-zoomin"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-zoomout"><span class="ui-icon ui-icon-zoomout"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-search"><span class="ui-icon ui-icon-search"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-wrench"><span class="ui-icon ui-icon-wrench"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-gear"><span class="ui-icon ui-icon-gear"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-heart"><span class="ui-icon ui-icon-heart"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-star"><span class="ui-icon ui-icon-star"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-link"><span class="ui-icon ui-icon-link"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-cancel"><span class="ui-icon ui-icon-cancel"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-plus"><span class="ui-icon ui-icon-plus"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-plusthick"><span class="ui-icon ui-icon-plusthick"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-minus"><span class="ui-icon ui-icon-minus"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-minusthick"><span class="ui-icon ui-icon-minusthick"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-close"><span class="ui-icon ui-icon-close"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-closethick"><span class="ui-icon ui-icon-closethick"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-key"><span class="ui-icon ui-icon-key"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-lightbulb"><span class="ui-icon ui-icon-lightbulb"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-scissors"><span class="ui-icon ui-icon-scissors"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-clipboard"><span class="ui-icon ui-icon-clipboard"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-copy"><span class="ui-icon ui-icon-copy"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-contact"><span class="ui-icon ui-icon-contact"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-image"><span class="ui-icon ui-icon-image"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-video"><span class="ui-icon ui-icon-video"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-script"><span class="ui-icon ui-icon-script"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-alert"><span class="ui-icon ui-icon-alert"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-info"><span class="ui-icon ui-icon-info"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-notice"><span class="ui-icon ui-icon-notice"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-help"><span class="ui-icon ui-icon-help"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-check"><span class="ui-icon ui-icon-check"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-bullet"><span class="ui-icon ui-icon-bullet"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-radio-off"><span class="ui-icon ui-icon-radio-off"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-radio-on"><span class="ui-icon ui-icon-radio-on"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-pin-w"><span class="ui-icon ui-icon-pin-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-pin-s"><span class="ui-icon ui-icon-pin-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-play"><span class="ui-icon ui-icon-play"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-pause"><span class="ui-icon ui-icon-pause"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-seek-next"><span class="ui-icon ui-icon-seek-next"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-seek-prev"><span class="ui-icon ui-icon-seek-prev"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-seek-end"><span class="ui-icon ui-icon-seek-end"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-seek-first"><span class="ui-icon ui-icon-seek-first"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-stop"><span class="ui-icon ui-icon-stop"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-eject"><span class="ui-icon ui-icon-eject"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-volume-off"><span class="ui-icon ui-icon-volume-off"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-volume-on"><span class="ui-icon ui-icon-volume-on"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-power"><span class="ui-icon ui-icon-power"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-signal-diag"><span class="ui-icon ui-icon-signal-diag"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-signal"><span class="ui-icon ui-icon-signal"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-battery-0"><span class="ui-icon ui-icon-battery-0"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-battery-1"><span class="ui-icon ui-icon-battery-1"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-battery-2"><span class="ui-icon ui-icon-battery-2"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-battery-3"><span class="ui-icon ui-icon-battery-3"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-plus"><span class="ui-icon ui-icon-circle-plus"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-minus"><span class="ui-icon ui-icon-circle-minus"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-close"><span class="ui-icon ui-icon-circle-close"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-triangle-e"><span class="ui-icon ui-icon-circle-triangle-e"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-triangle-s"><span class="ui-icon ui-icon-circle-triangle-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-triangle-w"><span class="ui-icon ui-icon-circle-triangle-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-triangle-n"><span class="ui-icon ui-icon-circle-triangle-n"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-arrow-e"><span class="ui-icon ui-icon-circle-arrow-e"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-arrow-s"><span class="ui-icon ui-icon-circle-arrow-s"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-arrow-w"><span class="ui-icon ui-icon-circle-arrow-w"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-arrow-n"><span class="ui-icon ui-icon-circle-arrow-n"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-zoomin"><span class="ui-icon ui-icon-circle-zoomin"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-zoomout"><span class="ui-icon ui-icon-circle-zoomout"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circle-check"><span class="ui-icon ui-icon-circle-check"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circlesmall-plus"><span class="ui-icon ui-icon-circlesmall-plus"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circlesmall-minus"><span class="ui-icon ui-icon-circlesmall-minus"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-circlesmall-close"><span class="ui-icon ui-icon-circlesmall-close"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-squaresmall-plus"><span class="ui-icon ui-icon-squaresmall-plus"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-squaresmall-minus"><span class="ui-icon ui-icon-squaresmall-minus"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-squaresmall-close"><span class="ui-icon ui-icon-squaresmall-close"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-grip-dotted-vertical"><span class="ui-icon ui-icon-grip-dotted-vertical"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-grip-dotted-horizontal"><span class="ui-icon ui-icon-grip-dotted-horizontal"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-grip-solid-vertical"><span class="ui-icon ui-icon-grip-solid-vertical"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-grip-solid-horizontal"><span class="ui-icon ui-icon-grip-solid-horizontal"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-gripsmall-diagonal-se"><span class="ui-icon ui-icon-gripsmall-diagonal-se"></span></li>
+	<li class="ui-state-default ui-corner-all" title=".ui-icon-grip-diagonal-se"><span class="ui-icon ui-icon-grip-diagonal-se"></span></li>
+</ul>
+
+
+<!-- Slider -->
+<h2 class="demoHeaders">Slider</h2>
+<div id="slider"></div>
+
+
+
+<!-- Datepicker -->
+<h2 class="demoHeaders">Datepicker</h2>
+<div id="datepicker"></div>
+
+
+
+<!-- Progressbar -->
+<h2 class="demoHeaders">Progressbar</h2>
+<div id="progressbar"></div>
+
+
+
+<!-- Progressbar -->
+<h2 class="demoHeaders">Selectmenu</h2>
+<select id="selectmenu">
+	<option>Slower</option>
+	<option>Slow</option>
+	<option selected="selected">Medium</option>
+	<option>Fast</option>
+	<option>Faster</option>
+</select>
+
+
+
+<!-- Spinner -->
+<h2 class="demoHeaders">Spinner</h2>
+<input id="spinner">
+
+
+
+<!-- Menu -->
+<h2 class="demoHeaders">Menu</h2>
+<ul style="width:100px;" id="menu">
+	<li>Item 1</li>
+	<li>Item 2</li>
+	<li>Item 3
+		<ul>
+			<li>Item 3-1</li>
+			<li>Item 3-2</li>
+			<li>Item 3-3</li>
+			<li>Item 3-4</li>
+			<li>Item 3-5</li>
+		</ul>
+	</li>
+	<li>Item 4</li>
+	<li>Item 5</li>
+</ul>
+
+
+
+<!-- Tooltip -->
+<h2 class="demoHeaders">Tooltip</h2>
+<p id="tooltip">
+	<a href="#" title="That&apos;s what this widget is">Tooltips</a> can be attached to any element. When you hover
+the element with your mouse, the title attribute is displayed in a little box next to the element, just like a native tooltip.
+</p>
+
+
+<!-- Highlight / Error -->
+<h2 class="demoHeaders">Highlight / Error</h2>
+<div class="ui-widget">
+	<div class="ui-state-highlight ui-corner-all" style="margin-top: 20px; padding: 0 .7em;">
+		<p><span class="ui-icon ui-icon-info" style="float: left; margin-right: .3em;"></span>
+		<strong>Hey!</strong> Sample ui-state-highlight style.</p>
+	</div>
+</div>
+<br>
+<div class="ui-widget">
+	<div class="ui-state-error ui-corner-all" style="padding: 0 .7em;">
+		<p><span class="ui-icon ui-icon-alert" style="float: left; margin-right: .3em;"></span>
+		<strong>Alert:</strong> Sample ui-state-error style.</p>
+	</div>
+</div>
+
+<script src="external/jquery/jquery.js"></script>
+<script src="jquery-ui.js"></script>
+<script>
+
+$( "#accordion" ).accordion();
+
+
+
+var availableTags = [
+	"ActionScript",
+	"AppleScript",
+	"Asp",
+	"BASIC",
+	"C",
+	"C++",
+	"Clojure",
+	"COBOL",
+	"ColdFusion",
+	"Erlang",
+	"Fortran",
+	"Groovy",
+	"Haskell",
+	"Java",
+	"JavaScript",
+	"Lisp",
+	"Perl",
+	"PHP",
+	"Python",
+	"Ruby",
+	"Scala",
+	"Scheme"
+];
+$( "#autocomplete" ).autocomplete({
+	source: availableTags
+});
+
+
+
+$( "#button" ).button();
+$( "#radioset" ).buttonset();
+
+
+
+$( "#tabs" ).tabs();
+
+
+
+$( "#dialog" ).dialog({
+	autoOpen: false,
+	width: 400,
+	buttons: [
+		{
+			text: "Ok",
+			click: function() {
+				$( this ).dialog( "close" );
+			}
+		},
+		{
+			text: "Cancel",
+			click: function() {
+				$( this ).dialog( "close" );
+			}
+		}
+	]
+});
+
+// Link to open the dialog
+$( "#dialog-link" ).click(function( event ) {
+	$( "#dialog" ).dialog( "open" );
+	event.preventDefault();
+});
+
+
+
+$( "#datepicker" ).datepicker({
+	inline: true
+});
+
+
+
+$( "#slider" ).slider({
+	range: true,
+	values: [ 17, 67 ]
+});
+
+
+
+$( "#progressbar" ).progressbar({
+	value: 20
+});
+
+
+
+$( "#spinner" ).spinner();
+
+
+
+$( "#menu" ).menu();
+
+
+
+$( "#tooltip" ).tooltip();
+
+
+
+$( "#selectmenu" ).selectmenu();
+
+
+// Hover states on the static widgets
+$( "#dialog-link, #icons li" ).hover(
+	function() {
+		$( this ).addClass( "ui-state-hover" );
+	},
+	function() {
+		$( this ).removeClass( "ui-state-hover" );
+	}
+);
+</script>
+</body>
+</html>
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.css
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.css	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.css	(revision 2119)
@@ -0,0 +1,1225 @@
+/*! jQuery UI - v1.11.4 - 2015-04-09
+* http://jqueryui.com
+* Includes: core.css, draggable.css, resizable.css, selectable.css, sortable.css, accordion.css, autocomplete.css, button.css, datepicker.css, dialog.css, menu.css, progressbar.css, selectmenu.css, slider.css, spinner.css, tabs.css, tooltip.css, theme.css
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=2191c0&bgTextureHeader=gloss_wave&bgImgOpacityHeader=75&borderColorHeader=4297d7&fcHeader=eaf5f7&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=inset_hard&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=0078ae&bgColorDefault=0078ae&bgTextureDefault=glass&bgImgOpacityDefault=45&borderColorDefault=77d5f7&fcDefault=ffffff&iconColorDefault=e0fdff&bgColorHover=79c9ec&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=448dae&fcHover=026890&iconColorHover=056b93&bgColorActive=6eac2c&bgTextureActive=gloss_wave&bgImgOpacityActive=50&borderColorActive=acdd4a&fcActive=ffffff&iconColorActive=f5e175&bgColorHighlight=f8da4e&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcd113&fcHighlight=915608&iconColorHighlight=f7a50d&bgColorError=e14f1c&bgTextureError=gloss_wave&bgImgOpacityError=45&borderColorError=cd0a0a&fcError=ffffff&iconColorError=fcd113&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=75&opacityOverlay=30&bgColorShadow=999999&bgTextureShadow=flat&bgImgOpacityShadow=55&opacityShadow=45&thicknessShadow=0px&offsetTopShadow=5px&offsetLeftShadow=5px&cornerRadiusShadow=5px
+* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden {
+	display: none;
+}
+.ui-helper-hidden-accessible {
+	border: 0;
+	clip: rect(0 0 0 0);
+	height: 1px;
+	margin: -1px;
+	overflow: hidden;
+	padding: 0;
+	position: absolute;
+	width: 1px;
+}
+.ui-helper-reset {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	outline: 0;
+	line-height: 1.3;
+	text-decoration: none;
+	font-size: 100%;
+	list-style: none;
+}
+.ui-helper-clearfix:before,
+.ui-helper-clearfix:after {
+	content: "";
+	display: table;
+	border-collapse: collapse;
+}
+.ui-helper-clearfix:after {
+	clear: both;
+}
+.ui-helper-clearfix {
+	min-height: 0; /* support: IE7 */
+}
+.ui-helper-zfix {
+	width: 100%;
+	height: 100%;
+	top: 0;
+	left: 0;
+	position: absolute;
+	opacity: 0;
+	filter:Alpha(Opacity=0); /* support: IE8 */
+}
+
+.ui-front {
+	z-index: 100;
+}
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled {
+	cursor: default !important;
+}
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon {
+	display: block;
+	text-indent: -99999px;
+	overflow: hidden;
+	background-repeat: no-repeat;
+}
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay {
+	position: fixed;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+}
+.ui-draggable-handle {
+	-ms-touch-action: none;
+	touch-action: none;
+}
+.ui-resizable {
+	position: relative;
+}
+.ui-resizable-handle {
+	position: absolute;
+	font-size: 0.1px;
+	display: block;
+	-ms-touch-action: none;
+	touch-action: none;
+}
+.ui-resizable-disabled .ui-resizable-handle,
+.ui-resizable-autohide .ui-resizable-handle {
+	display: none;
+}
+.ui-resizable-n {
+	cursor: n-resize;
+	height: 7px;
+	width: 100%;
+	top: -5px;
+	left: 0;
+}
+.ui-resizable-s {
+	cursor: s-resize;
+	height: 7px;
+	width: 100%;
+	bottom: -5px;
+	left: 0;
+}
+.ui-resizable-e {
+	cursor: e-resize;
+	width: 7px;
+	right: -5px;
+	top: 0;
+	height: 100%;
+}
+.ui-resizable-w {
+	cursor: w-resize;
+	width: 7px;
+	left: -5px;
+	top: 0;
+	height: 100%;
+}
+.ui-resizable-se {
+	cursor: se-resize;
+	width: 12px;
+	height: 12px;
+	right: 1px;
+	bottom: 1px;
+}
+.ui-resizable-sw {
+	cursor: sw-resize;
+	width: 9px;
+	height: 9px;
+	left: -5px;
+	bottom: -5px;
+}
+.ui-resizable-nw {
+	cursor: nw-resize;
+	width: 9px;
+	height: 9px;
+	left: -5px;
+	top: -5px;
+}
+.ui-resizable-ne {
+	cursor: ne-resize;
+	width: 9px;
+	height: 9px;
+	right: -5px;
+	top: -5px;
+}
+.ui-selectable {
+	-ms-touch-action: none;
+	touch-action: none;
+}
+.ui-selectable-helper {
+	position: absolute;
+	z-index: 100;
+	border: 1px dotted black;
+}
+.ui-sortable-handle {
+	-ms-touch-action: none;
+	touch-action: none;
+}
+.ui-accordion .ui-accordion-header {
+	display: block;
+	cursor: pointer;
+	position: relative;
+	margin: 2px 0 0 0;
+	padding: .5em .5em .5em .7em;
+	min-height: 0; /* support: IE7 */
+	font-size: 100%;
+}
+.ui-accordion .ui-accordion-icons {
+	padding-left: 2.2em;
+}
+.ui-accordion .ui-accordion-icons .ui-accordion-icons {
+	padding-left: 2.2em;
+}
+.ui-accordion .ui-accordion-header .ui-accordion-header-icon {
+	position: absolute;
+	left: .5em;
+	top: 50%;
+	margin-top: -8px;
+}
+.ui-accordion .ui-accordion-content {
+	padding: 1em 2.2em;
+	border-top: 0;
+	overflow: auto;
+}
+.ui-autocomplete {
+	position: absolute;
+	top: 0;
+	left: 0;
+	cursor: default;
+}
+.ui-button {
+	display: inline-block;
+	position: relative;
+	padding: 0;
+	line-height: normal;
+	margin-right: .1em;
+	cursor: pointer;
+	vertical-align: middle;
+	text-align: center;
+	overflow: visible; /* removes extra width in IE */
+}
+.ui-button,
+.ui-button:link,
+.ui-button:visited,
+.ui-button:hover,
+.ui-button:active {
+	text-decoration: none;
+}
+/* to make room for the icon, a width needs to be set here */
+.ui-button-icon-only {
+	width: 2.2em;
+}
+/* button elements seem to need a little more width */
+button.ui-button-icon-only {
+	width: 2.4em;
+}
+.ui-button-icons-only {
+	width: 3.4em;
+}
+button.ui-button-icons-only {
+	width: 3.7em;
+}
+
+/* button text element */
+.ui-button .ui-button-text {
+	display: block;
+	line-height: normal;
+}
+.ui-button-text-only .ui-button-text {
+	padding: .4em 1em;
+}
+.ui-button-icon-only .ui-button-text,
+.ui-button-icons-only .ui-button-text {
+	padding: .4em;
+	text-indent: -9999999px;
+}
+.ui-button-text-icon-primary .ui-button-text,
+.ui-button-text-icons .ui-button-text {
+	padding: .4em 1em .4em 2.1em;
+}
+.ui-button-text-icon-secondary .ui-button-text,
+.ui-button-text-icons .ui-button-text {
+	padding: .4em 2.1em .4em 1em;
+}
+.ui-button-text-icons .ui-button-text {
+	padding-left: 2.1em;
+	padding-right: 2.1em;
+}
+/* no icon support for input elements, provide padding by default */
+input.ui-button {
+	padding: .4em 1em;
+}
+
+/* button icon element(s) */
+.ui-button-icon-only .ui-icon,
+.ui-button-text-icon-primary .ui-icon,
+.ui-button-text-icon-secondary .ui-icon,
+.ui-button-text-icons .ui-icon,
+.ui-button-icons-only .ui-icon {
+	position: absolute;
+	top: 50%;
+	margin-top: -8px;
+}
+.ui-button-icon-only .ui-icon {
+	left: 50%;
+	margin-left: -8px;
+}
+.ui-button-text-icon-primary .ui-button-icon-primary,
+.ui-button-text-icons .ui-button-icon-primary,
+.ui-button-icons-only .ui-button-icon-primary {
+	left: .5em;
+}
+.ui-button-text-icon-secondary .ui-button-icon-secondary,
+.ui-button-text-icons .ui-button-icon-secondary,
+.ui-button-icons-only .ui-button-icon-secondary {
+	right: .5em;
+}
+
+/* button sets */
+.ui-buttonset {
+	margin-right: 7px;
+}
+.ui-buttonset .ui-button {
+	margin-left: 0;
+	margin-right: -.3em;
+}
+
+/* workarounds */
+/* reset extra padding in Firefox, see h5bp.com/l */
+input.ui-button::-moz-focus-inner,
+button.ui-button::-moz-focus-inner {
+	border: 0;
+	padding: 0;
+}
+.ui-datepicker {
+	width: 17em;
+	padding: .2em .2em 0;
+	display: none;
+}
+.ui-datepicker .ui-datepicker-header {
+	position: relative;
+	padding: .2em 0;
+}
+.ui-datepicker .ui-datepicker-prev,
+.ui-datepicker .ui-datepicker-next {
+	position: absolute;
+	top: 2px;
+	width: 1.8em;
+	height: 1.8em;
+}
+.ui-datepicker .ui-datepicker-prev-hover,
+.ui-datepicker .ui-datepicker-next-hover {
+	top: 1px;
+}
+.ui-datepicker .ui-datepicker-prev {
+	left: 2px;
+}
+.ui-datepicker .ui-datepicker-next {
+	right: 2px;
+}
+.ui-datepicker .ui-datepicker-prev-hover {
+	left: 1px;
+}
+.ui-datepicker .ui-datepicker-next-hover {
+	right: 1px;
+}
+.ui-datepicker .ui-datepicker-prev span,
+.ui-datepicker .ui-datepicker-next span {
+	display: block;
+	position: absolute;
+	left: 50%;
+	margin-left: -8px;
+	top: 50%;
+	margin-top: -8px;
+}
+.ui-datepicker .ui-datepicker-title {
+	margin: 0 2.3em;
+	line-height: 1.8em;
+	text-align: center;
+}
+.ui-datepicker .ui-datepicker-title select {
+	font-size: 1em;
+	margin: 1px 0;
+}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year {
+	width: 45%;
+}
+.ui-datepicker table {
+	width: 100%;
+	font-size: .9em;
+	border-collapse: collapse;
+	margin: 0 0 .4em;
+}
+.ui-datepicker th {
+	padding: .7em .3em;
+	text-align: center;
+	font-weight: bold;
+	border: 0;
+}
+.ui-datepicker td {
+	border: 0;
+	padding: 1px;
+}
+.ui-datepicker td span,
+.ui-datepicker td a {
+	display: block;
+	padding: .2em;
+	text-align: right;
+	text-decoration: none;
+}
+.ui-datepicker .ui-datepicker-buttonpane {
+	background-image: none;
+	margin: .7em 0 0 0;
+	padding: 0 .2em;
+	border-left: 0;
+	border-right: 0;
+	border-bottom: 0;
+}
+.ui-datepicker .ui-datepicker-buttonpane button {
+	float: right;
+	margin: .5em .2em .4em;
+	cursor: pointer;
+	padding: .2em .6em .3em .6em;
+	width: auto;
+	overflow: visible;
+}
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
+	float: left;
+}
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi {
+	width: auto;
+}
+.ui-datepicker-multi .ui-datepicker-group {
+	float: left;
+}
+.ui-datepicker-multi .ui-datepicker-group table {
+	width: 95%;
+	margin: 0 auto .4em;
+}
+.ui-datepicker-multi-2 .ui-datepicker-group {
+	width: 50%;
+}
+.ui-datepicker-multi-3 .ui-datepicker-group {
+	width: 33.3%;
+}
+.ui-datepicker-multi-4 .ui-datepicker-group {
+	width: 25%;
+}
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
+	border-left-width: 0;
+}
+.ui-datepicker-multi .ui-datepicker-buttonpane {
+	clear: left;
+}
+.ui-datepicker-row-break {
+	clear: both;
+	width: 100%;
+	font-size: 0;
+}
+
+/* RTL support */
+.ui-datepicker-rtl {
+	direction: rtl;
+}
+.ui-datepicker-rtl .ui-datepicker-prev {
+	right: 2px;
+	left: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-next {
+	left: 2px;
+	right: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-prev:hover {
+	right: 1px;
+	left: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-next:hover {
+	left: 1px;
+	right: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane {
+	clear: right;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane button {
+	float: left;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
+.ui-datepicker-rtl .ui-datepicker-group {
+	float: right;
+}
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
+	border-right-width: 0;
+	border-left-width: 1px;
+}
+.ui-dialog {
+	overflow: hidden;
+	position: absolute;
+	top: 0;
+	left: 0;
+	padding: .2em;
+	outline: 0;
+}
+.ui-dialog .ui-dialog-titlebar {
+	padding: .4em 1em;
+	position: relative;
+}
+.ui-dialog .ui-dialog-title {
+	float: left;
+	margin: .1em 0;
+	white-space: nowrap;
+	width: 90%;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+.ui-dialog .ui-dialog-titlebar-close {
+	position: absolute;
+	right: .3em;
+	top: 50%;
+	width: 20px;
+	margin: -10px 0 0 0;
+	padding: 1px;
+	height: 20px;
+}
+.ui-dialog .ui-dialog-content {
+	position: relative;
+	border: 0;
+	padding: .5em 1em;
+	background: none;
+	overflow: auto;
+}
+.ui-dialog .ui-dialog-buttonpane {
+	text-align: left;
+	border-width: 1px 0 0 0;
+	background-image: none;
+	margin-top: .5em;
+	padding: .3em 1em .5em .4em;
+}
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
+	float: right;
+}
+.ui-dialog .ui-dialog-buttonpane button {
+	margin: .5em .4em .5em 0;
+	cursor: pointer;
+}
+.ui-dialog .ui-resizable-se {
+	width: 12px;
+	height: 12px;
+	right: -5px;
+	bottom: -5px;
+	background-position: 16px 16px;
+}
+.ui-draggable .ui-dialog-titlebar {
+	cursor: move;
+}
+.ui-menu {
+	list-style: none;
+	padding: 0;
+	margin: 0;
+	display: block;
+	outline: none;
+}
+.ui-menu .ui-menu {
+	position: absolute;
+}
+.ui-menu .ui-menu-item {
+	position: relative;
+	margin: 0;
+	padding: 3px 1em 3px .4em;
+	cursor: pointer;
+	min-height: 0; /* support: IE7 */
+	/* support: IE10, see #8844 */
+	list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
+}
+.ui-menu .ui-menu-divider {
+	margin: 5px 0;
+	height: 0;
+	font-size: 0;
+	line-height: 0;
+	border-width: 1px 0 0 0;
+}
+.ui-menu .ui-state-focus,
+.ui-menu .ui-state-active {
+	margin: -1px;
+}
+
+/* icon support */
+.ui-menu-icons {
+	position: relative;
+}
+.ui-menu-icons .ui-menu-item {
+	padding-left: 2em;
+}
+
+/* left-aligned */
+.ui-menu .ui-icon {
+	position: absolute;
+	top: 0;
+	bottom: 0;
+	left: .2em;
+	margin: auto 0;
+}
+
+/* right-aligned */
+.ui-menu .ui-menu-icon {
+	left: auto;
+	right: 0;
+}
+.ui-progressbar {
+	height: 2em;
+	text-align: left;
+	overflow: hidden;
+}
+.ui-progressbar .ui-progressbar-value {
+	margin: -1px;
+	height: 100%;
+}
+.ui-progressbar .ui-progressbar-overlay {
+	background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");
+	height: 100%;
+	filter: alpha(opacity=25); /* support: IE8 */
+	opacity: 0.25;
+}
+.ui-progressbar-indeterminate .ui-progressbar-value {
+	background-image: none;
+}
+.ui-selectmenu-menu {
+	padding: 0;
+	margin: 0;
+	position: absolute;
+	top: 0;
+	left: 0;
+	display: none;
+}
+.ui-selectmenu-menu .ui-menu {
+	overflow: auto;
+	/* Support: IE7 */
+	overflow-x: hidden;
+	padding-bottom: 1px;
+}
+.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup {
+	font-size: 1em;
+	font-weight: bold;
+	line-height: 1.5;
+	padding: 2px 0.4em;
+	margin: 0.5em 0 0 0;
+	height: auto;
+	border: 0;
+}
+.ui-selectmenu-open {
+	display: block;
+}
+.ui-selectmenu-button {
+	display: inline-block;
+	overflow: hidden;
+	position: relative;
+	text-decoration: none;
+	cursor: pointer;
+}
+.ui-selectmenu-button span.ui-icon {
+	right: 0.5em;
+	left: auto;
+	margin-top: -8px;
+	position: absolute;
+	top: 50%;
+}
+.ui-selectmenu-button span.ui-selectmenu-text {
+	text-align: left;
+	padding: 0.4em 2.1em 0.4em 1em;
+	display: block;
+	line-height: 1.4;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+.ui-slider {
+	position: relative;
+	text-align: left;
+}
+.ui-slider .ui-slider-handle {
+	position: absolute;
+	z-index: 2;
+	width: 1.2em;
+	height: 1.2em;
+	cursor: default;
+	-ms-touch-action: none;
+	touch-action: none;
+}
+.ui-slider .ui-slider-range {
+	position: absolute;
+	z-index: 1;
+	font-size: .7em;
+	display: block;
+	border: 0;
+	background-position: 0 0;
+}
+
+/* support: IE8 - See #6727 */
+.ui-slider.ui-state-disabled .ui-slider-handle,
+.ui-slider.ui-state-disabled .ui-slider-range {
+	filter: inherit;
+}
+
+.ui-slider-horizontal {
+	height: .8em;
+}
+.ui-slider-horizontal .ui-slider-handle {
+	top: -.3em;
+	margin-left: -.6em;
+}
+.ui-slider-horizontal .ui-slider-range {
+	top: 0;
+	height: 100%;
+}
+.ui-slider-horizontal .ui-slider-range-min {
+	left: 0;
+}
+.ui-slider-horizontal .ui-slider-range-max {
+	right: 0;
+}
+
+.ui-slider-vertical {
+	width: .8em;
+	height: 100px;
+}
+.ui-slider-vertical .ui-slider-handle {
+	left: -.3em;
+	margin-left: 0;
+	margin-bottom: -.6em;
+}
+.ui-slider-vertical .ui-slider-range {
+	left: 0;
+	width: 100%;
+}
+.ui-slider-vertical .ui-slider-range-min {
+	bottom: 0;
+}
+.ui-slider-vertical .ui-slider-range-max {
+	top: 0;
+}
+.ui-spinner {
+	position: relative;
+	display: inline-block;
+	overflow: hidden;
+	padding: 0;
+	vertical-align: middle;
+}
+.ui-spinner-input {
+	border: none;
+	background: none;
+	color: inherit;
+	padding: 0;
+	margin: .2em 0;
+	vertical-align: middle;
+	margin-left: .4em;
+	margin-right: 22px;
+}
+.ui-spinner-button {
+	width: 16px;
+	height: 50%;
+	font-size: .5em;
+	padding: 0;
+	margin: 0;
+	text-align: center;
+	position: absolute;
+	cursor: default;
+	display: block;
+	overflow: hidden;
+	right: 0;
+}
+/* more specificity required here to override default borders */
+.ui-spinner a.ui-spinner-button {
+	border-top: none;
+	border-bottom: none;
+	border-right: none;
+}
+/* vertically center icon */
+.ui-spinner .ui-icon {
+	position: absolute;
+	margin-top: -8px;
+	top: 50%;
+	left: 0;
+}
+.ui-spinner-up {
+	top: 0;
+}
+.ui-spinner-down {
+	bottom: 0;
+}
+
+/* TR overrides */
+.ui-spinner .ui-icon-triangle-1-s {
+	/* need to fix icons sprite */
+	background-position: -65px -16px;
+}
+.ui-tabs {
+	position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+	padding: .2em;
+}
+.ui-tabs .ui-tabs-nav {
+	margin: 0;
+	padding: .2em .2em 0;
+}
+.ui-tabs .ui-tabs-nav li {
+	list-style: none;
+	float: left;
+	position: relative;
+	top: 0;
+	margin: 1px .2em 0 0;
+	border-bottom-width: 0;
+	padding: 0;
+	white-space: nowrap;
+}
+.ui-tabs .ui-tabs-nav .ui-tabs-anchor {
+	float: left;
+	padding: .5em 1em;
+	text-decoration: none;
+}
+.ui-tabs .ui-tabs-nav li.ui-tabs-active {
+	margin-bottom: -1px;
+	padding-bottom: 1px;
+}
+.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,
+.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,
+.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor {
+	cursor: text;
+}
+.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor {
+	cursor: pointer;
+}
+.ui-tabs .ui-tabs-panel {
+	display: block;
+	border-width: 0;
+	padding: 1em 1.4em;
+	background: none;
+}
+.ui-tooltip {
+	padding: 8px;
+	position: absolute;
+	z-index: 9999;
+	max-width: 300px;
+	-webkit-box-shadow: 0 0 5px #aaa;
+	box-shadow: 0 0 5px #aaa;
+}
+body .ui-tooltip {
+	border-width: 2px;
+}
+
+/* Component containers
+----------------------------------*/
+.ui-widget {
+	font-family: Verdana,Arial,sans-serif;
+	font-size: 1.1em;
+}
+.ui-widget .ui-widget {
+	font-size: 1em;
+}
+.ui-widget input,
+.ui-widget select,
+.ui-widget textarea,
+.ui-widget button {
+	font-family: Verdana,Arial,sans-serif;
+	font-size: 1em;
+}
+.ui-widget-content {
+	border: 1px solid #a6c9e2;
+	background: #fcfdfd url("images/ui-bg_inset-hard_100_fcfdfd_1x100.png") 50% bottom repeat-x;
+	color: #222222;
+}
+.ui-widget-content a {
+	color: #222222;
+}
+.ui-widget-header {
+	border: 1px solid #4297d7;
+	background: #2191c0 url("images/ui-bg_gloss-wave_75_2191c0_500x100.png") 50% 50% repeat-x;
+	color: #eaf5f7;
+	font-weight: bold;
+}
+.ui-widget-header a {
+	color: #eaf5f7;
+}
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default,
+.ui-widget-content .ui-state-default,
+.ui-widget-header .ui-state-default {
+	border: 1px solid #77d5f7;
+	background: #0078ae url("images/ui-bg_glass_45_0078ae_1x400.png") 50% 50% repeat-x;
+	font-weight: normal;
+	color: #ffffff;
+}
+.ui-state-default a,
+.ui-state-default a:link,
+.ui-state-default a:visited {
+	color: #ffffff;
+	text-decoration: none;
+}
+.ui-state-hover,
+.ui-widget-content .ui-state-hover,
+.ui-widget-header .ui-state-hover,
+.ui-state-focus,
+.ui-widget-content .ui-state-focus,
+.ui-widget-header .ui-state-focus {
+	border: 1px solid #448dae;
+	background: #79c9ec url("images/ui-bg_glass_75_79c9ec_1x400.png") 50% 50% repeat-x;
+	font-weight: normal;
+	color: #026890;
+}
+.ui-state-hover a,
+.ui-state-hover a:hover,
+.ui-state-hover a:link,
+.ui-state-hover a:visited,
+.ui-state-focus a,
+.ui-state-focus a:hover,
+.ui-state-focus a:link,
+.ui-state-focus a:visited {
+	color: #026890;
+	text-decoration: none;
+}
+.ui-state-active,
+.ui-widget-content .ui-state-active,
+.ui-widget-header .ui-state-active {
+	border: 1px solid #acdd4a;
+	background: #6eac2c url("images/ui-bg_gloss-wave_50_6eac2c_500x100.png") 50% 50% repeat-x;
+	font-weight: normal;
+	color: #ffffff;
+}
+.ui-state-active a,
+.ui-state-active a:link,
+.ui-state-active a:visited {
+	//color: #ffffff;
+	text-decoration: none;
+}
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight,
+.ui-widget-content .ui-state-highlight,
+.ui-widget-header .ui-state-highlight {
+	border: 1px solid #fcd113;
+	background: #f8da4e url("images/ui-bg_glass_55_f8da4e_1x400.png") 50% 50% repeat-x;
+	color: #915608;
+}
+.ui-state-highlight a,
+.ui-widget-content .ui-state-highlight a,
+.ui-widget-header .ui-state-highlight a {
+	color: #915608;
+}
+.ui-state-error,
+.ui-widget-content .ui-state-error,
+.ui-widget-header .ui-state-error {
+	border: 1px solid #cd0a0a;
+	background: #e14f1c url("images/ui-bg_gloss-wave_45_e14f1c_500x100.png") 50% top repeat-x;
+	color: #ffffff;
+}
+.ui-state-error a,
+.ui-widget-content .ui-state-error a,
+.ui-widget-header .ui-state-error a {
+	color: #ffffff;
+}
+.ui-state-error-text,
+.ui-widget-content .ui-state-error-text,
+.ui-widget-header .ui-state-error-text {
+	color: #ffffff;
+}
+.ui-priority-primary,
+.ui-widget-content .ui-priority-primary,
+.ui-widget-header .ui-priority-primary {
+	font-weight: bold;
+}
+.ui-priority-secondary,
+.ui-widget-content .ui-priority-secondary,
+.ui-widget-header .ui-priority-secondary {
+	opacity: .7;
+	filter:Alpha(Opacity=70); /* support: IE8 */
+	font-weight: normal;
+}
+.ui-state-disabled,
+.ui-widget-content .ui-state-disabled,
+.ui-widget-header .ui-state-disabled {
+	opacity: .35;
+	filter:Alpha(Opacity=35); /* support: IE8 */
+	background-image: none;
+}
+.ui-state-disabled .ui-icon {
+	filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
+}
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon {
+	width: 16px;
+	height: 16px;
+}
+.ui-icon,
+.ui-widget-content .ui-icon {
+	background-image: url("images/ui-icons_0078ae_256x240.png");
+}
+.ui-widget-header .ui-icon {
+	background-image: url("images/ui-icons_d8e7f3_256x240.png");
+}
+.ui-state-default .ui-icon {
+	background-image: url("images/ui-icons_e0fdff_256x240.png");
+}
+.ui-state-hover .ui-icon,
+.ui-state-focus .ui-icon {
+	background-image: url("images/ui-icons_056b93_256x240.png");
+}
+.ui-state-active .ui-icon {
+	background-image: url("images/ui-icons_f5e175_256x240.png");
+}
+.ui-state-highlight .ui-icon {
+	background-image: url("images/ui-icons_f7a50d_256x240.png");
+}
+.ui-state-error .ui-icon,
+.ui-state-error-text .ui-icon {
+	background-image: url("images/ui-icons_fcd113_256x240.png");
+}
+
+/* positioning */
+.ui-icon-blank { background-position: 16px 16px; }
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-on { background-position: -96px -144px; }
+.ui-icon-radio-off { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all,
+.ui-corner-top,
+.ui-corner-left,
+.ui-corner-tl {
+	border-top-left-radius: 5px;
+}
+.ui-corner-all,
+.ui-corner-top,
+.ui-corner-right,
+.ui-corner-tr {
+	border-top-right-radius: 5px;
+}
+.ui-corner-all,
+.ui-corner-bottom,
+.ui-corner-left,
+.ui-corner-bl {
+	border-bottom-left-radius: 5px;
+}
+.ui-corner-all,
+.ui-corner-bottom,
+.ui-corner-right,
+.ui-corner-br {
+	border-bottom-right-radius: 5px;
+}
+
+/* Overlays */
+.ui-widget-overlay {
+	background: #aaaaaa url("images/ui-bg_flat_75_aaaaaa_40x100.png") 50% 50% repeat-x;
+	opacity: .3;
+	filter: Alpha(Opacity=30); /* support: IE8 */
+}
+.ui-widget-shadow {
+	margin: 5px 0 0 5px;
+	padding: 0px;
+	background: #999999 url("images/ui-bg_flat_55_999999_40x100.png") 50% 50% repeat-x;
+	opacity: .45;
+	filter: Alpha(Opacity=45); /* support: IE8 */
+	border-radius: 5px;
+}
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.js	(revision 2119)
@@ -0,0 +1,16617 @@
+/*! jQuery UI - v1.11.4 - 2015-04-05
+* http://jqueryui.com
+* Includes: core.js, widget.js, mouse.js, position.js, draggable.js, droppable.js, resizable.js, selectable.js, sortable.js, accordion.js, autocomplete.js, button.js, datepicker.js, dialog.js, menu.js, progressbar.js, selectmenu.js, slider.js, spinner.js, tabs.js, tooltip.js, effect.js, effect-blind.js, effect-bounce.js, effect-clip.js, effect-drop.js, effect-explode.js, effect-fade.js, effect-fold.js, effect-highlight.js, effect-puff.js, effect-pulsate.js, effect-scale.js, effect-shake.js, effect-size.js, effect-slide.js, effect-transfer.js
+* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
+
+(function( factory ) {
+	if ( typeof define === "function" && define.amd ) {
+
+		// AMD. Register as an anonymous module.
+		define([ "jquery" ], factory );
+	} else {
+
+		// Browser globals
+		factory( jQuery );
+	}
+}(function( $ ) {
+/*!
+ * jQuery UI Core 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/category/ui-core/
+ */
+
+
+// $.ui might exist from components with no dependencies, e.g., $.ui.position
+$.ui = $.ui || {};
+
+$.extend( $.ui, {
+	version: "1.11.4",
+
+	keyCode: {
+		BACKSPACE: 8,
+		COMMA: 188,
+		DELETE: 46,
+		DOWN: 40,
+		END: 35,
+		ENTER: 13,
+		ESCAPE: 27,
+		HOME: 36,
+		LEFT: 37,
+		PAGE_DOWN: 34,
+		PAGE_UP: 33,
+		PERIOD: 190,
+		RIGHT: 39,
+		SPACE: 32,
+		TAB: 9,
+		UP: 38
+	}
+});
+
+// plugins
+$.fn.extend({
+	scrollParent: function( includeHidden ) {
+		var position = this.css( "position" ),
+			excludeStaticParent = position === "absolute",
+			overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/,
+			scrollParent = this.parents().filter( function() {
+				var parent = $( this );
+				if ( excludeStaticParent && parent.css( "position" ) === "static" ) {
+					return false;
+				}
+				return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + parent.css( "overflow-x" ) );
+			}).eq( 0 );
+
+		return position === "fixed" || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent;
+	},
+
+	uniqueId: (function() {
+		var uuid = 0;
+
+		return function() {
+			return this.each(function() {
+				if ( !this.id ) {
+					this.id = "ui-id-" + ( ++uuid );
+				}
+			});
+		};
+	})(),
+
+	removeUniqueId: function() {
+		return this.each(function() {
+			if ( /^ui-id-\d+$/.test( this.id ) ) {
+				$( this ).removeAttr( "id" );
+			}
+		});
+	}
+});
+
+// selectors
+function focusable( element, isTabIndexNotNaN ) {
+	var map, mapName, img,
+		nodeName = element.nodeName.toLowerCase();
+	if ( "area" === nodeName ) {
+		map = element.parentNode;
+		mapName = map.name;
+		if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
+			return false;
+		}
+		img = $( "img[usemap='#" + mapName + "']" )[ 0 ];
+		return !!img && visible( img );
+	}
+	return ( /^(input|select|textarea|button|object)$/.test( nodeName ) ?
+		!element.disabled :
+		"a" === nodeName ?
+			element.href || isTabIndexNotNaN :
+			isTabIndexNotNaN) &&
+		// the element and all of its ancestors must be visible
+		visible( element );
+}
+
+function visible( element ) {
+	return $.expr.filters.visible( element ) &&
+		!$( element ).parents().addBack().filter(function() {
+			return $.css( this, "visibility" ) === "hidden";
+		}).length;
+}
+
+$.extend( $.expr[ ":" ], {
+	data: $.expr.createPseudo ?
+		$.expr.createPseudo(function( dataName ) {
+			return function( elem ) {
+				return !!$.data( elem, dataName );
+			};
+		}) :
+		// support: jQuery <1.8
+		function( elem, i, match ) {
+			return !!$.data( elem, match[ 3 ] );
+		},
+
+	focusable: function( element ) {
+		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
+	},
+
+	tabbable: function( element ) {
+		var tabIndex = $.attr( element, "tabindex" ),
+			isTabIndexNaN = isNaN( tabIndex );
+		return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
+	}
+});
+
+// support: jQuery <1.8
+if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
+	$.each( [ "Width", "Height" ], function( i, name ) {
+		var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
+			type = name.toLowerCase(),
+			orig = {
+				innerWidth: $.fn.innerWidth,
+				innerHeight: $.fn.innerHeight,
+				outerWidth: $.fn.outerWidth,
+				outerHeight: $.fn.outerHeight
+			};
+
+		function reduce( elem, size, border, margin ) {
+			$.each( side, function() {
+				size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
+				if ( border ) {
+					size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
+				}
+				if ( margin ) {
+					size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
+				}
+			});
+			return size;
+		}
+
+		$.fn[ "inner" + name ] = function( size ) {
+			if ( size === undefined ) {
+				return orig[ "inner" + name ].call( this );
+			}
+
+			return this.each(function() {
+				$( this ).css( type, reduce( this, size ) + "px" );
+			});
+		};
+
+		$.fn[ "outer" + name] = function( size, margin ) {
+			if ( typeof size !== "number" ) {
+				return orig[ "outer" + name ].call( this, size );
+			}
+
+			return this.each(function() {
+				$( this).css( type, reduce( this, size, true, margin ) + "px" );
+			});
+		};
+	});
+}
+
+// support: jQuery <1.8
+if ( !$.fn.addBack ) {
+	$.fn.addBack = function( selector ) {
+		return this.add( selector == null ?
+			this.prevObject : this.prevObject.filter( selector )
+		);
+	};
+}
+
+// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
+if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
+	$.fn.removeData = (function( removeData ) {
+		return function( key ) {
+			if ( arguments.length ) {
+				return removeData.call( this, $.camelCase( key ) );
+			} else {
+				return removeData.call( this );
+			}
+		};
+	})( $.fn.removeData );
+}
+
+// deprecated
+$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
+
+$.fn.extend({
+	focus: (function( orig ) {
+		return function( delay, fn ) {
+			return typeof delay === "number" ?
+				this.each(function() {
+					var elem = this;
+					setTimeout(function() {
+						$( elem ).focus();
+						if ( fn ) {
+							fn.call( elem );
+						}
+					}, delay );
+				}) :
+				orig.apply( this, arguments );
+		};
+	})( $.fn.focus ),
+
+	disableSelection: (function() {
+		var eventType = "onselectstart" in document.createElement( "div" ) ?
+			"selectstart" :
+			"mousedown";
+
+		return function() {
+			return this.bind( eventType + ".ui-disableSelection", function( event ) {
+				event.preventDefault();
+			});
+		};
+	})(),
+
+	enableSelection: function() {
+		return this.unbind( ".ui-disableSelection" );
+	},
+
+	zIndex: function( zIndex ) {
+		if ( zIndex !== undefined ) {
+			return this.css( "zIndex", zIndex );
+		}
+
+		if ( this.length ) {
+			var elem = $( this[ 0 ] ), position, value;
+			while ( elem.length && elem[ 0 ] !== document ) {
+				// Ignore z-index if position is set to a value where z-index is ignored by the browser
+				// This makes behavior of this function consistent across browsers
+				// WebKit always returns auto if the element is positioned
+				position = elem.css( "position" );
+				if ( position === "absolute" || position === "relative" || position === "fixed" ) {
+					// IE returns 0 when zIndex is not specified
+					// other browsers return a string
+					// we ignore the case of nested elements with an explicit value of 0
+					// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
+					value = parseInt( elem.css( "zIndex" ), 10 );
+					if ( !isNaN( value ) && value !== 0 ) {
+						return value;
+					}
+				}
+				elem = elem.parent();
+			}
+		}
+
+		return 0;
+	}
+});
+
+// $.ui.plugin is deprecated. Use $.widget() extensions instead.
+$.ui.plugin = {
+	add: function( module, option, set ) {
+		var i,
+			proto = $.ui[ module ].prototype;
+		for ( i in set ) {
+			proto.plugins[ i ] = proto.plugins[ i ] || [];
+			proto.plugins[ i ].push( [ option, set[ i ] ] );
+		}
+	},
+	call: function( instance, name, args, allowDisconnected ) {
+		var i,
+			set = instance.plugins[ name ];
+
+		if ( !set ) {
+			return;
+		}
+
+		if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) ) {
+			return;
+		}
+
+		for ( i = 0; i < set.length; i++ ) {
+			if ( instance.options[ set[ i ][ 0 ] ] ) {
+				set[ i ][ 1 ].apply( instance.element, args );
+			}
+		}
+	}
+};
+
+
+/*!
+ * jQuery UI Widget 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/jQuery.widget/
+ */
+
+
+var widget_uuid = 0,
+	widget_slice = Array.prototype.slice;
+
+$.cleanData = (function( orig ) {
+	return function( elems ) {
+		var events, elem, i;
+		for ( i = 0; (elem = elems[i]) != null; i++ ) {
+			try {
+
+				// Only trigger remove when necessary to save time
+				events = $._data( elem, "events" );
+				if ( events && events.remove ) {
+					$( elem ).triggerHandler( "remove" );
+				}
+
+			// http://bugs.jquery.com/ticket/8235
+			} catch ( e ) {}
+		}
+		orig( elems );
+	};
+})( $.cleanData );
+
+$.widget = function( name, base, prototype ) {
+	var fullName, existingConstructor, constructor, basePrototype,
+		// proxiedPrototype allows the provided prototype to remain unmodified
+		// so that it can be used as a mixin for multiple widgets (#8876)
+		proxiedPrototype = {},
+		namespace = name.split( "." )[ 0 ];
+
+	name = name.split( "." )[ 1 ];
+	fullName = namespace + "-" + name;
+
+	if ( !prototype ) {
+		prototype = base;
+		base = $.Widget;
+	}
+
+	// create selector for plugin
+	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+		return !!$.data( elem, fullName );
+	};
+
+	$[ namespace ] = $[ namespace ] || {};
+	existingConstructor = $[ namespace ][ name ];
+	constructor = $[ namespace ][ name ] = function( options, element ) {
+		// allow instantiation without "new" keyword
+		if ( !this._createWidget ) {
+			return new constructor( options, element );
+		}
+
+		// allow instantiation without initializing for simple inheritance
+		// must use "new" keyword (the code above always passes args)
+		if ( arguments.length ) {
+			this._createWidget( options, element );
+		}
+	};
+	// extend with the existing constructor to carry over any static properties
+	$.extend( constructor, existingConstructor, {
+		version: prototype.version,
+		// copy the object used to create the prototype in case we need to
+		// redefine the widget later
+		_proto: $.extend( {}, prototype ),
+		// track widgets that inherit from this widget in case this widget is
+		// redefined after a widget inherits from it
+		_childConstructors: []
+	});
+
+	basePrototype = new base();
+	// we need to make the options hash a property directly on the new instance
+	// otherwise we'll modify the options hash on the prototype that we're
+	// inheriting from
+	basePrototype.options = $.widget.extend( {}, basePrototype.options );
+	$.each( prototype, function( prop, value ) {
+		if ( !$.isFunction( value ) ) {
+			proxiedPrototype[ prop ] = value;
+			return;
+		}
+		proxiedPrototype[ prop ] = (function() {
+			var _super = function() {
+					return base.prototype[ prop ].apply( this, arguments );
+				},
+				_superApply = function( args ) {
+					return base.prototype[ prop ].apply( this, args );
+				};
+			return function() {
+				var __super = this._super,
+					__superApply = this._superApply,
+					returnValue;
+
+				this._super = _super;
+				this._superApply = _superApply;
+
+				returnValue = value.apply( this, arguments );
+
+				this._super = __super;
+				this._superApply = __superApply;
+
+				return returnValue;
+			};
+		})();
+	});
+	constructor.prototype = $.widget.extend( basePrototype, {
+		// TODO: remove support for widgetEventPrefix
+		// always use the name + a colon as the prefix, e.g., draggable:start
+		// don't prefix for widgets that aren't DOM-based
+		widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
+	}, proxiedPrototype, {
+		constructor: constructor,
+		namespace: namespace,
+		widgetName: name,
+		widgetFullName: fullName
+	});
+
+	// If this widget is being redefined then we need to find all widgets that
+	// are inheriting from it and redefine all of them so that they inherit from
+	// the new version of this widget. We're essentially trying to replace one
+	// level in the prototype chain.
+	if ( existingConstructor ) {
+		$.each( existingConstructor._childConstructors, function( i, child ) {
+			var childPrototype = child.prototype;
+
+			// redefine the child widget using the same prototype that was
+			// originally used, but inherit from the new version of the base
+			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
+		});
+		// remove the list of existing child constructors from the old constructor
+		// so the old child constructors can be garbage collected
+		delete existingConstructor._childConstructors;
+	} else {
+		base._childConstructors.push( constructor );
+	}
+
+	$.widget.bridge( name, constructor );
+
+	return constructor;
+};
+
+$.widget.extend = function( target ) {
+	var input = widget_slice.call( arguments, 1 ),
+		inputIndex = 0,
+		inputLength = input.length,
+		key,
+		value;
+	for ( ; inputIndex < inputLength; inputIndex++ ) {
+		for ( key in input[ inputIndex ] ) {
+			value = input[ inputIndex ][ key ];
+			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+				// Clone objects
+				if ( $.isPlainObject( value ) ) {
+					target[ key ] = $.isPlainObject( target[ key ] ) ?
+						$.widget.extend( {}, target[ key ], value ) :
+						// Don't extend strings, arrays, etc. with objects
+						$.widget.extend( {}, value );
+				// Copy everything else by reference
+				} else {
+					target[ key ] = value;
+				}
+			}
+		}
+	}
+	return target;
+};
+
+$.widget.bridge = function( name, object ) {
+	var fullName = object.prototype.widgetFullName || name;
+	$.fn[ name ] = function( options ) {
+		var isMethodCall = typeof options === "string",
+			args = widget_slice.call( arguments, 1 ),
+			returnValue = this;
+
+		if ( isMethodCall ) {
+			this.each(function() {
+				var methodValue,
+					instance = $.data( this, fullName );
+				if ( options === "instance" ) {
+					returnValue = instance;
+					return false;
+				}
+				if ( !instance ) {
+					return $.error( "cannot call methods on " + name + " prior to initialization; " +
+						"attempted to call method '" + options + "'" );
+				}
+				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
+					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
+				}
+				methodValue = instance[ options ].apply( instance, args );
+				if ( methodValue !== instance && methodValue !== undefined ) {
+					returnValue = methodValue && methodValue.jquery ?
+						returnValue.pushStack( methodValue.get() ) :
+						methodValue;
+					return false;
+				}
+			});
+		} else {
+
+			// Allow multiple hashes to be passed on init
+			if ( args.length ) {
+				options = $.widget.extend.apply( null, [ options ].concat(args) );
+			}
+
+			this.each(function() {
+				var instance = $.data( this, fullName );
+				if ( instance ) {
+					instance.option( options || {} );
+					if ( instance._init ) {
+						instance._init();
+					}
+				} else {
+					$.data( this, fullName, new object( options, this ) );
+				}
+			});
+		}
+
+		return returnValue;
+	};
+};
+
+$.Widget = function( /* options, element */ ) {};
+$.Widget._childConstructors = [];
+
+$.Widget.prototype = {
+	widgetName: "widget",
+	widgetEventPrefix: "",
+	defaultElement: "<div>",
+	options: {
+		disabled: false,
+
+		// callbacks
+		create: null
+	},
+	_createWidget: function( options, element ) {
+		element = $( element || this.defaultElement || this )[ 0 ];
+		this.element = $( element );
+		this.uuid = widget_uuid++;
+		this.eventNamespace = "." + this.widgetName + this.uuid;
+
+		this.bindings = $();
+		this.hoverable = $();
+		this.focusable = $();
+
+		if ( element !== this ) {
+			$.data( element, this.widgetFullName, this );
+			this._on( true, this.element, {
+				remove: function( event ) {
+					if ( event.target === element ) {
+						this.destroy();
+					}
+				}
+			});
+			this.document = $( element.style ?
+				// element within the document
+				element.ownerDocument :
+				// element is window or document
+				element.document || element );
+			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
+		}
+
+		this.options = $.widget.extend( {},
+			this.options,
+			this._getCreateOptions(),
+			options );
+
+		this._create();
+		this._trigger( "create", null, this._getCreateEventData() );
+		this._init();
+	},
+	_getCreateOptions: $.noop,
+	_getCreateEventData: $.noop,
+	_create: $.noop,
+	_init: $.noop,
+
+	destroy: function() {
+		this._destroy();
+		// we can probably remove the unbind calls in 2.0
+		// all event bindings should go through this._on()
+		this.element
+			.unbind( this.eventNamespace )
+			.removeData( this.widgetFullName )
+			// support: jquery <1.6.3
+			// http://bugs.jquery.com/ticket/9413
+			.removeData( $.camelCase( this.widgetFullName ) );
+		this.widget()
+			.unbind( this.eventNamespace )
+			.removeAttr( "aria-disabled" )
+			.removeClass(
+				this.widgetFullName + "-disabled " +
+				"ui-state-disabled" );
+
+		// clean up events and states
+		this.bindings.unbind( this.eventNamespace );
+		this.hoverable.removeClass( "ui-state-hover" );
+		this.focusable.removeClass( "ui-state-focus" );
+	},
+	_destroy: $.noop,
+
+	widget: function() {
+		return this.element;
+	},
+
+	option: function( key, value ) {
+		var options = key,
+			parts,
+			curOption,
+			i;
+
+		if ( arguments.length === 0 ) {
+			// don't return a reference to the internal hash
+			return $.widget.extend( {}, this.options );
+		}
+
+		if ( typeof key === "string" ) {
+			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+			options = {};
+			parts = key.split( "." );
+			key = parts.shift();
+			if ( parts.length ) {
+				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+				for ( i = 0; i < parts.length - 1; i++ ) {
+					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+					curOption = curOption[ parts[ i ] ];
+				}
+				key = parts.pop();
+				if ( arguments.length === 1 ) {
+					return curOption[ key ] === undefined ? null : curOption[ key ];
+				}
+				curOption[ key ] = value;
+			} else {
+				if ( arguments.length === 1 ) {
+					return this.options[ key ] === undefined ? null : this.options[ key ];
+				}
+				options[ key ] = value;
+			}
+		}
+
+		this._setOptions( options );
+
+		return this;
+	},
+	_setOptions: function( options ) {
+		var key;
+
+		for ( key in options ) {
+			this._setOption( key, options[ key ] );
+		}
+
+		return this;
+	},
+	_setOption: function( key, value ) {
+		this.options[ key ] = value;
+
+		if ( key === "disabled" ) {
+			this.widget()
+				.toggleClass( this.widgetFullName + "-disabled", !!value );
+
+			// If the widget is becoming disabled, then nothing is interactive
+			if ( value ) {
+				this.hoverable.removeClass( "ui-state-hover" );
+				this.focusable.removeClass( "ui-state-focus" );
+			}
+		}
+
+		return this;
+	},
+
+	enable: function() {
+		return this._setOptions({ disabled: false });
+	},
+	disable: function() {
+		return this._setOptions({ disabled: true });
+	},
+
+	_on: function( suppressDisabledCheck, element, handlers ) {
+		var delegateElement,
+			instance = this;
+
+		// no suppressDisabledCheck flag, shuffle arguments
+		if ( typeof suppressDisabledCheck !== "boolean" ) {
+			handlers = element;
+			element = suppressDisabledCheck;
+			suppressDisabledCheck = false;
+		}
+
+		// no element argument, shuffle and use this.element
+		if ( !handlers ) {
+			handlers = element;
+			element = this.element;
+			delegateElement = this.widget();
+		} else {
+			element = delegateElement = $( element );
+			this.bindings = this.bindings.add( element );
+		}
+
+		$.each( handlers, function( event, handler ) {
+			function handlerProxy() {
+				// allow widgets to customize the disabled handling
+				// - disabled as an array instead of boolean
+				// - disabled class as method for disabling individual parts
+				if ( !suppressDisabledCheck &&
+						( instance.options.disabled === true ||
+							$( this ).hasClass( "ui-state-disabled" ) ) ) {
+					return;
+				}
+				return ( typeof handler === "string" ? instance[ handler ] : handler )
+					.apply( instance, arguments );
+			}
+
+			// copy the guid so direct unbinding works
+			if ( typeof handler !== "string" ) {
+				handlerProxy.guid = handler.guid =
+					handler.guid || handlerProxy.guid || $.guid++;
+			}
+
+			var match = event.match( /^([\w:-]*)\s*(.*)$/ ),
+				eventName = match[1] + instance.eventNamespace,
+				selector = match[2];
+			if ( selector ) {
+				delegateElement.delegate( selector, eventName, handlerProxy );
+			} else {
+				element.bind( eventName, handlerProxy );
+			}
+		});
+	},
+
+	_off: function( element, eventName ) {
+		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) +
+			this.eventNamespace;
+		element.unbind( eventName ).undelegate( eventName );
+
+		// Clear the stack to avoid memory leaks (#10056)
+		this.bindings = $( this.bindings.not( element ).get() );
+		this.focusable = $( this.focusable.not( element ).get() );
+		this.hoverable = $( this.hoverable.not( element ).get() );
+	},
+
+	_delay: function( handler, delay ) {
+		function handlerProxy() {
+			return ( typeof handler === "string" ? instance[ handler ] : handler )
+				.apply( instance, arguments );
+		}
+		var instance = this;
+		return setTimeout( handlerProxy, delay || 0 );
+	},
+
+	_hoverable: function( element ) {
+		this.hoverable = this.hoverable.add( element );
+		this._on( element, {
+			mouseenter: function( event ) {
+				$( event.currentTarget ).addClass( "ui-state-hover" );
+			},
+			mouseleave: function( event ) {
+				$( event.currentTarget ).removeClass( "ui-state-hover" );
+			}
+		});
+	},
+
+	_focusable: function( element ) {
+		this.focusable = this.focusable.add( element );
+		this._on( element, {
+			focusin: function( event ) {
+				$( event.currentTarget ).addClass( "ui-state-focus" );
+			},
+			focusout: function( event ) {
+				$( event.currentTarget ).removeClass( "ui-state-focus" );
+			}
+		});
+	},
+
+	_trigger: function( type, event, data ) {
+		var prop, orig,
+			callback = this.options[ type ];
+
+		data = data || {};
+		event = $.Event( event );
+		event.type = ( type === this.widgetEventPrefix ?
+			type :
+			this.widgetEventPrefix + type ).toLowerCase();
+		// the original event may come from any element
+		// so we need to reset the target on the new event
+		event.target = this.element[ 0 ];
+
+		// copy original event properties over to the new event
+		orig = event.originalEvent;
+		if ( orig ) {
+			for ( prop in orig ) {
+				if ( !( prop in event ) ) {
+					event[ prop ] = orig[ prop ];
+				}
+			}
+		}
+
+		this.element.trigger( event, data );
+		return !( $.isFunction( callback ) &&
+			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
+			event.isDefaultPrevented() );
+	}
+};
+
+$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+		if ( typeof options === "string" ) {
+			options = { effect: options };
+		}
+		var hasOptions,
+			effectName = !options ?
+				method :
+				options === true || typeof options === "number" ?
+					defaultEffect :
+					options.effect || defaultEffect;
+		options = options || {};
+		if ( typeof options === "number" ) {
+			options = { duration: options };
+		}
+		hasOptions = !$.isEmptyObject( options );
+		options.complete = callback;
+		if ( options.delay ) {
+			element.delay( options.delay );
+		}
+		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
+			element[ method ]( options );
+		} else if ( effectName !== method && element[ effectName ] ) {
+			element[ effectName ]( options.duration, options.easing, callback );
+		} else {
+			element.queue(function( next ) {
+				$( this )[ method ]();
+				if ( callback ) {
+					callback.call( element[ 0 ] );
+				}
+				next();
+			});
+		}
+	};
+});
+
+var widget = $.widget;
+
+
+/*!
+ * jQuery UI Mouse 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/mouse/
+ */
+
+
+var mouseHandled = false;
+$( document ).mouseup( function() {
+	mouseHandled = false;
+});
+
+var mouse = $.widget("ui.mouse", {
+	version: "1.11.4",
+	options: {
+		cancel: "input,textarea,button,select,option",
+		distance: 1,
+		delay: 0
+	},
+	_mouseInit: function() {
+		var that = this;
+
+		this.element
+			.bind("mousedown." + this.widgetName, function(event) {
+				return that._mouseDown(event);
+			})
+			.bind("click." + this.widgetName, function(event) {
+				if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
+					$.removeData(event.target, that.widgetName + ".preventClickEvent");
+					event.stopImmediatePropagation();
+					return false;
+				}
+			});
+
+		this.started = false;
+	},
+
+	// TODO: make sure destroying one instance of mouse doesn't mess with
+	// other instances of mouse
+	_mouseDestroy: function() {
+		this.element.unbind("." + this.widgetName);
+		if ( this._mouseMoveDelegate ) {
+			this.document
+				.unbind("mousemove." + this.widgetName, this._mouseMoveDelegate)
+				.unbind("mouseup." + this.widgetName, this._mouseUpDelegate);
+		}
+	},
+
+	_mouseDown: function(event) {
+		// don't let more than one widget handle mouseStart
+		if ( mouseHandled ) {
+			return;
+		}
+
+		this._mouseMoved = false;
+
+		// we may have missed mouseup (out of window)
+		(this._mouseStarted && this._mouseUp(event));
+
+		this._mouseDownEvent = event;
+
+		var that = this,
+			btnIsLeft = (event.which === 1),
+			// event.target.nodeName works around a bug in IE 8 with
+			// disabled inputs (#7620)
+			elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
+		if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
+			return true;
+		}
+
+		this.mouseDelayMet = !this.options.delay;
+		if (!this.mouseDelayMet) {
+			this._mouseDelayTimer = setTimeout(function() {
+				that.mouseDelayMet = true;
+			}, this.options.delay);
+		}
+
+		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+			this._mouseStarted = (this._mouseStart(event) !== false);
+			if (!this._mouseStarted) {
+				event.preventDefault();
+				return true;
+			}
+		}
+
+		// Click event may never have fired (Gecko & Opera)
+		if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
+			$.removeData(event.target, this.widgetName + ".preventClickEvent");
+		}
+
+		// these delegates are required to keep context
+		this._mouseMoveDelegate = function(event) {
+			return that._mouseMove(event);
+		};
+		this._mouseUpDelegate = function(event) {
+			return that._mouseUp(event);
+		};
+
+		this.document
+			.bind( "mousemove." + this.widgetName, this._mouseMoveDelegate )
+			.bind( "mouseup." + this.widgetName, this._mouseUpDelegate );
+
+		event.preventDefault();
+
+		mouseHandled = true;
+		return true;
+	},
+
+	_mouseMove: function(event) {
+		// Only check for mouseups outside the document if you've moved inside the document
+		// at least once. This prevents the firing of mouseup in the case of IE<9, which will
+		// fire a mousemove event if content is placed under the cursor. See #7778
+		// Support: IE <9
+		if ( this._mouseMoved ) {
+			// IE mouseup check - mouseup happened when mouse was out of window
+			if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
+				return this._mouseUp(event);
+
+			// Iframe mouseup check - mouseup occurred in another document
+			} else if ( !event.which ) {
+				return this._mouseUp( event );
+			}
+		}
+
+		if ( event.which || event.button ) {
+			this._mouseMoved = true;
+		}
+
+		if (this._mouseStarted) {
+			this._mouseDrag(event);
+			return event.preventDefault();
+		}
+
+		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+			this._mouseStarted =
+				(this._mouseStart(this._mouseDownEvent, event) !== false);
+			(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
+		}
+
+		return !this._mouseStarted;
+	},
+
+	_mouseUp: function(event) {
+		this.document
+			.unbind( "mousemove." + this.widgetName, this._mouseMoveDelegate )
+			.unbind( "mouseup." + this.widgetName, this._mouseUpDelegate );
+
+		if (this._mouseStarted) {
+			this._mouseStarted = false;
+
+			if (event.target === this._mouseDownEvent.target) {
+				$.data(event.target, this.widgetName + ".preventClickEvent", true);
+			}
+
+			this._mouseStop(event);
+		}
+
+		mouseHandled = false;
+		return false;
+	},
+
+	_mouseDistanceMet: function(event) {
+		return (Math.max(
+				Math.abs(this._mouseDownEvent.pageX - event.pageX),
+				Math.abs(this._mouseDownEvent.pageY - event.pageY)
+			) >= this.options.distance
+		);
+	},
+
+	_mouseDelayMet: function(/* event */) {
+		return this.mouseDelayMet;
+	},
+
+	// These are placeholder methods, to be overriden by extending plugin
+	_mouseStart: function(/* event */) {},
+	_mouseDrag: function(/* event */) {},
+	_mouseStop: function(/* event */) {},
+	_mouseCapture: function(/* event */) { return true; }
+});
+
+
+/*!
+ * jQuery UI Position 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/position/
+ */
+
+(function() {
+
+$.ui = $.ui || {};
+
+var cachedScrollbarWidth, supportsOffsetFractions,
+	max = Math.max,
+	abs = Math.abs,
+	round = Math.round,
+	rhorizontal = /left|center|right/,
+	rvertical = /top|center|bottom/,
+	roffset = /[\+\-]\d+(\.[\d]+)?%?/,
+	rposition = /^\w+/,
+	rpercent = /%$/,
+	_position = $.fn.position;
+
+function getOffsets( offsets, width, height ) {
+	return [
+		parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
+		parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
+	];
+}
+
+function parseCss( element, property ) {
+	return parseInt( $.css( element, property ), 10 ) || 0;
+}
+
+function getDimensions( elem ) {
+	var raw = elem[0];
+	if ( raw.nodeType === 9 ) {
+		return {
+			width: elem.width(),
+			height: elem.height(),
+			offset: { top: 0, left: 0 }
+		};
+	}
+	if ( $.isWindow( raw ) ) {
+		return {
+			width: elem.width(),
+			height: elem.height(),
+			offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
+		};
+	}
+	if ( raw.preventDefault ) {
+		return {
+			width: 0,
+			height: 0,
+			offset: { top: raw.pageY, left: raw.pageX }
+		};
+	}
+	return {
+		width: elem.outerWidth(),
+		height: elem.outerHeight(),
+		offset: elem.offset()
+	};
+}
+
+$.position = {
+	scrollbarWidth: function() {
+		if ( cachedScrollbarWidth !== undefined ) {
+			return cachedScrollbarWidth;
+		}
+		var w1, w2,
+			div = $( "<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
+			innerDiv = div.children()[0];
+
+		$( "body" ).append( div );
+		w1 = innerDiv.offsetWidth;
+		div.css( "overflow", "scroll" );
+
+		w2 = innerDiv.offsetWidth;
+
+		if ( w1 === w2 ) {
+			w2 = div[0].clientWidth;
+		}
+
+		div.remove();
+
+		return (cachedScrollbarWidth = w1 - w2);
+	},
+	getScrollInfo: function( within ) {
+		var overflowX = within.isWindow || within.isDocument ? "" :
+				within.element.css( "overflow-x" ),
+			overflowY = within.isWindow || within.isDocument ? "" :
+				within.element.css( "overflow-y" ),
+			hasOverflowX = overflowX === "scroll" ||
+				( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
+			hasOverflowY = overflowY === "scroll" ||
+				( overflowY === "auto" && within.height < within.element[0].scrollHeight );
+		return {
+			width: hasOverflowY ? $.position.scrollbarWidth() : 0,
+			height: hasOverflowX ? $.position.scrollbarWidth() : 0
+		};
+	},
+	getWithinInfo: function( element ) {
+		var withinElement = $( element || window ),
+			isWindow = $.isWindow( withinElement[0] ),
+			isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9;
+		return {
+			element: withinElement,
+			isWindow: isWindow,
+			isDocument: isDocument,
+			offset: withinElement.offset() || { left: 0, top: 0 },
+			scrollLeft: withinElement.scrollLeft(),
+			scrollTop: withinElement.scrollTop(),
+
+			// support: jQuery 1.6.x
+			// jQuery 1.6 doesn't support .outerWidth/Height() on documents or windows
+			width: isWindow || isDocument ? withinElement.width() : withinElement.outerWidth(),
+			height: isWindow || isDocument ? withinElement.height() : withinElement.outerHeight()
+		};
+	}
+};
+
+$.fn.position = function( options ) {
+	if ( !options || !options.of ) {
+		return _position.apply( this, arguments );
+	}
+
+	// make a copy, we don't want to modify arguments
+	options = $.extend( {}, options );
+
+	var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
+		target = $( options.of ),
+		within = $.position.getWithinInfo( options.within ),
+		scrollInfo = $.position.getScrollInfo( within ),
+		collision = ( options.collision || "flip" ).split( " " ),
+		offsets = {};
+
+	dimensions = getDimensions( target );
+	if ( target[0].preventDefault ) {
+		// force left top to allow flipping
+		options.at = "left top";
+	}
+	targetWidth = dimensions.width;
+	targetHeight = dimensions.height;
+	targetOffset = dimensions.offset;
+	// clone to reuse original targetOffset later
+	basePosition = $.extend( {}, targetOffset );
+
+	// force my and at to have valid horizontal and vertical positions
+	// if a value is missing or invalid, it will be converted to center
+	$.each( [ "my", "at" ], function() {
+		var pos = ( options[ this ] || "" ).split( " " ),
+			horizontalOffset,
+			verticalOffset;
+
+		if ( pos.length === 1) {
+			pos = rhorizontal.test( pos[ 0 ] ) ?
+				pos.concat( [ "center" ] ) :
+				rvertical.test( pos[ 0 ] ) ?
+					[ "center" ].concat( pos ) :
+					[ "center", "center" ];
+		}
+		pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
+		pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
+
+		// calculate offsets
+		horizontalOffset = roffset.exec( pos[ 0 ] );
+		verticalOffset = roffset.exec( pos[ 1 ] );
+		offsets[ this ] = [
+			horizontalOffset ? horizontalOffset[ 0 ] : 0,
+			verticalOffset ? verticalOffset[ 0 ] : 0
+		];
+
+		// reduce to just the positions without the offsets
+		options[ this ] = [
+			rposition.exec( pos[ 0 ] )[ 0 ],
+			rposition.exec( pos[ 1 ] )[ 0 ]
+		];
+	});
+
+	// normalize collision option
+	if ( collision.length === 1 ) {
+		collision[ 1 ] = collision[ 0 ];
+	}
+
+	if ( options.at[ 0 ] === "right" ) {
+		basePosition.left += targetWidth;
+	} else if ( options.at[ 0 ] === "center" ) {
+		basePosition.left += targetWidth / 2;
+	}
+
+	if ( options.at[ 1 ] === "bottom" ) {
+		basePosition.top += targetHeight;
+	} else if ( options.at[ 1 ] === "center" ) {
+		basePosition.top += targetHeight / 2;
+	}
+
+	atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
+	basePosition.left += atOffset[ 0 ];
+	basePosition.top += atOffset[ 1 ];
+
+	return this.each(function() {
+		var collisionPosition, using,
+			elem = $( this ),
+			elemWidth = elem.outerWidth(),
+			elemHeight = elem.outerHeight(),
+			marginLeft = parseCss( this, "marginLeft" ),
+			marginTop = parseCss( this, "marginTop" ),
+			collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
+			collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
+			position = $.extend( {}, basePosition ),
+			myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
+
+		if ( options.my[ 0 ] === "right" ) {
+			position.left -= elemWidth;
+		} else if ( options.my[ 0 ] === "center" ) {
+			position.left -= elemWidth / 2;
+		}
+
+		if ( options.my[ 1 ] === "bottom" ) {
+			position.top -= elemHeight;
+		} else if ( options.my[ 1 ] === "center" ) {
+			position.top -= elemHeight / 2;
+		}
+
+		position.left += myOffset[ 0 ];
+		position.top += myOffset[ 1 ];
+
+		// if the browser doesn't support fractions, then round for consistent results
+		if ( !supportsOffsetFractions ) {
+			position.left = round( position.left );
+			position.top = round( position.top );
+		}
+
+		collisionPosition = {
+			marginLeft: marginLeft,
+			marginTop: marginTop
+		};
+
+		$.each( [ "left", "top" ], function( i, dir ) {
+			if ( $.ui.position[ collision[ i ] ] ) {
+				$.ui.position[ collision[ i ] ][ dir ]( position, {
+					targetWidth: targetWidth,
+					targetHeight: targetHeight,
+					elemWidth: elemWidth,
+					elemHeight: elemHeight,
+					collisionPosition: collisionPosition,
+					collisionWidth: collisionWidth,
+					collisionHeight: collisionHeight,
+					offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
+					my: options.my,
+					at: options.at,
+					within: within,
+					elem: elem
+				});
+			}
+		});
+
+		if ( options.using ) {
+			// adds feedback as second argument to using callback, if present
+			using = function( props ) {
+				var left = targetOffset.left - position.left,
+					right = left + targetWidth - elemWidth,
+					top = targetOffset.top - position.top,
+					bottom = top + targetHeight - elemHeight,
+					feedback = {
+						target: {
+							element: target,
+							left: targetOffset.left,
+							top: targetOffset.top,
+							width: targetWidth,
+							height: targetHeight
+						},
+						element: {
+							element: elem,
+							left: position.left,
+							top: position.top,
+							width: elemWidth,
+							height: elemHeight
+						},
+						horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
+						vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
+					};
+				if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
+					feedback.horizontal = "center";
+				}
+				if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
+					feedback.vertical = "middle";
+				}
+				if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
+					feedback.important = "horizontal";
+				} else {
+					feedback.important = "vertical";
+				}
+				options.using.call( this, props, feedback );
+			};
+		}
+
+		elem.offset( $.extend( position, { using: using } ) );
+	});
+};
+
+$.ui.position = {
+	fit: {
+		left: function( position, data ) {
+			var within = data.within,
+				withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
+				outerWidth = within.width,
+				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+				overLeft = withinOffset - collisionPosLeft,
+				overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
+				newOverRight;
+
+			// element is wider than within
+			if ( data.collisionWidth > outerWidth ) {
+				// element is initially over the left side of within
+				if ( overLeft > 0 && overRight <= 0 ) {
+					newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
+					position.left += overLeft - newOverRight;
+				// element is initially over right side of within
+				} else if ( overRight > 0 && overLeft <= 0 ) {
+					position.left = withinOffset;
+				// element is initially over both left and right sides of within
+				} else {
+					if ( overLeft > overRight ) {
+						position.left = withinOffset + outerWidth - data.collisionWidth;
+					} else {
+						position.left = withinOffset;
+					}
+				}
+			// too far left -> align with left edge
+			} else if ( overLeft > 0 ) {
+				position.left += overLeft;
+			// too far right -> align with right edge
+			} else if ( overRight > 0 ) {
+				position.left -= overRight;
+			// adjust based on position and margin
+			} else {
+				position.left = max( position.left - collisionPosLeft, position.left );
+			}
+		},
+		top: function( position, data ) {
+			var within = data.within,
+				withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
+				outerHeight = data.within.height,
+				collisionPosTop = position.top - data.collisionPosition.marginTop,
+				overTop = withinOffset - collisionPosTop,
+				overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
+				newOverBottom;
+
+			// element is taller than within
+			if ( data.collisionHeight > outerHeight ) {
+				// element is initially over the top of within
+				if ( overTop > 0 && overBottom <= 0 ) {
+					newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
+					position.top += overTop - newOverBottom;
+				// element is initially over bottom of within
+				} else if ( overBottom > 0 && overTop <= 0 ) {
+					position.top = withinOffset;
+				// element is initially over both top and bottom of within
+				} else {
+					if ( overTop > overBottom ) {
+						position.top = withinOffset + outerHeight - data.collisionHeight;
+					} else {
+						position.top = withinOffset;
+					}
+				}
+			// too far up -> align with top
+			} else if ( overTop > 0 ) {
+				position.top += overTop;
+			// too far down -> align with bottom edge
+			} else if ( overBottom > 0 ) {
+				position.top -= overBottom;
+			// adjust based on position and margin
+			} else {
+				position.top = max( position.top - collisionPosTop, position.top );
+			}
+		}
+	},
+	flip: {
+		left: function( position, data ) {
+			var within = data.within,
+				withinOffset = within.offset.left + within.scrollLeft,
+				outerWidth = within.width,
+				offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
+				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+				overLeft = collisionPosLeft - offsetLeft,
+				overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
+				myOffset = data.my[ 0 ] === "left" ?
+					-data.elemWidth :
+					data.my[ 0 ] === "right" ?
+						data.elemWidth :
+						0,
+				atOffset = data.at[ 0 ] === "left" ?
+					data.targetWidth :
+					data.at[ 0 ] === "right" ?
+						-data.targetWidth :
+						0,
+				offset = -2 * data.offset[ 0 ],
+				newOverRight,
+				newOverLeft;
+
+			if ( overLeft < 0 ) {
+				newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
+				if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
+					position.left += myOffset + atOffset + offset;
+				}
+			} else if ( overRight > 0 ) {
+				newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
+				if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
+					position.left += myOffset + atOffset + offset;
+				}
+			}
+		},
+		top: function( position, data ) {
+			var within = data.within,
+				withinOffset = within.offset.top + within.scrollTop,
+				outerHeight = within.height,
+				offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
+				collisionPosTop = position.top - data.collisionPosition.marginTop,
+				overTop = collisionPosTop - offsetTop,
+				overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
+				top = data.my[ 1 ] === "top",
+				myOffset = top ?
+					-data.elemHeight :
+					data.my[ 1 ] === "bottom" ?
+						data.elemHeight :
+						0,
+				atOffset = data.at[ 1 ] === "top" ?
+					data.targetHeight :
+					data.at[ 1 ] === "bottom" ?
+						-data.targetHeight :
+						0,
+				offset = -2 * data.offset[ 1 ],
+				newOverTop,
+				newOverBottom;
+			if ( overTop < 0 ) {
+				newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
+				if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {
+					position.top += myOffset + atOffset + offset;
+				}
+			} else if ( overBottom > 0 ) {
+				newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
+				if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {
+					position.top += myOffset + atOffset + offset;
+				}
+			}
+		}
+	},
+	flipfit: {
+		left: function() {
+			$.ui.position.flip.left.apply( this, arguments );
+			$.ui.position.fit.left.apply( this, arguments );
+		},
+		top: function() {
+			$.ui.position.flip.top.apply( this, arguments );
+			$.ui.position.fit.top.apply( this, arguments );
+		}
+	}
+};
+
+// fraction support test
+(function() {
+	var testElement, testElementParent, testElementStyle, offsetLeft, i,
+		body = document.getElementsByTagName( "body" )[ 0 ],
+		div = document.createElement( "div" );
+
+	//Create a "fake body" for testing based on method used in jQuery.support
+	testElement = document.createElement( body ? "div" : "body" );
+	testElementStyle = {
+		visibility: "hidden",
+		width: 0,
+		height: 0,
+		border: 0,
+		margin: 0,
+		background: "none"
+	};
+	if ( body ) {
+		$.extend( testElementStyle, {
+			position: "absolute",
+			left: "-1000px",
+			top: "-1000px"
+		});
+	}
+	for ( i in testElementStyle ) {
+		testElement.style[ i ] = testElementStyle[ i ];
+	}
+	testElement.appendChild( div );
+	testElementParent = body || document.documentElement;
+	testElementParent.insertBefore( testElement, testElementParent.firstChild );
+
+	div.style.cssText = "position: absolute; left: 10.7432222px;";
+
+	offsetLeft = $( div ).offset().left;
+	supportsOffsetFractions = offsetLeft > 10 && offsetLeft < 11;
+
+	testElement.innerHTML = "";
+	testElementParent.removeChild( testElement );
+})();
+
+})();
+
+var position = $.ui.position;
+
+
+/*!
+ * jQuery UI Draggable 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/draggable/
+ */
+
+
+$.widget("ui.draggable", $.ui.mouse, {
+	version: "1.11.4",
+	widgetEventPrefix: "drag",
+	options: {
+		addClasses: true,
+		appendTo: "parent",
+		axis: false,
+		connectToSortable: false,
+		containment: false,
+		cursor: "auto",
+		cursorAt: false,
+		grid: false,
+		handle: false,
+		helper: "original",
+		iframeFix: false,
+		opacity: false,
+		refreshPositions: false,
+		revert: false,
+		revertDuration: 500,
+		scope: "default",
+		scroll: true,
+		scrollSensitivity: 20,
+		scrollSpeed: 20,
+		snap: false,
+		snapMode: "both",
+		snapTolerance: 20,
+		stack: false,
+		zIndex: false,
+
+		// callbacks
+		drag: null,
+		start: null,
+		stop: null
+	},
+	_create: function() {
+
+		if ( this.options.helper === "original" ) {
+			this._setPositionRelative();
+		}
+		if (this.options.addClasses){
+			this.element.addClass("ui-draggable");
+		}
+		if (this.options.disabled){
+			this.element.addClass("ui-draggable-disabled");
+		}
+		this._setHandleClassName();
+
+		this._mouseInit();
+	},
+
+	_setOption: function( key, value ) {
+		this._super( key, value );
+		if ( key === "handle" ) {
+			this._removeHandleClassName();
+			this._setHandleClassName();
+		}
+	},
+
+	_destroy: function() {
+		if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) {
+			this.destroyOnClear = true;
+			return;
+		}
+		this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
+		this._removeHandleClassName();
+		this._mouseDestroy();
+	},
+
+	_mouseCapture: function(event) {
+		var o = this.options;
+
+		this._blurActiveElement( event );
+
+		// among others, prevent a drag on a resizable-handle
+		if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) {
+			return false;
+		}
+
+		//Quit if we're not on a valid handle
+		this.handle = this._getHandle(event);
+		if (!this.handle) {
+			return false;
+		}
+
+		this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix );
+
+		return true;
+
+	},
+
+	_blockFrames: function( selector ) {
+		this.iframeBlocks = this.document.find( selector ).map(function() {
+			var iframe = $( this );
+
+			return $( "<div>" )
+				.css( "position", "absolute" )
+				.appendTo( iframe.parent() )
+				.outerWidth( iframe.outerWidth() )
+				.outerHeight( iframe.outerHeight() )
+				.offset( iframe.offset() )[ 0 ];
+		});
+	},
+
+	_unblockFrames: function() {
+		if ( this.iframeBlocks ) {
+			this.iframeBlocks.remove();
+			delete this.iframeBlocks;
+		}
+	},
+
+	_blurActiveElement: function( event ) {
+		var document = this.document[ 0 ];
+
+		// Only need to blur if the event occurred on the draggable itself, see #10527
+		if ( !this.handleElement.is( event.target ) ) {
+			return;
+		}
+
+		// support: IE9
+		// IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
+		try {
+
+			// Support: IE9, IE10
+			// If the <body> is blurred, IE will switch windows, see #9520
+			if ( document.activeElement && document.activeElement.nodeName.toLowerCase() !== "body" ) {
+
+				// Blur any element that currently has focus, see #4261
+				$( document.activeElement ).blur();
+			}
+		} catch ( error ) {}
+	},
+
+	_mouseStart: function(event) {
+
+		var o = this.options;
+
+		//Create and append the visible helper
+		this.helper = this._createHelper(event);
+
+		this.helper.addClass("ui-draggable-dragging");
+
+		//Cache the helper size
+		this._cacheHelperProportions();
+
+		//If ddmanager is used for droppables, set the global draggable
+		if ($.ui.ddmanager) {
+			$.ui.ddmanager.current = this;
+		}
+
+		/*
+		 * - Position generation -
+		 * This block generates everything position related - it's the core of draggables.
+		 */
+
+		//Cache the margins of the original element
+		this._cacheMargins();
+
+		//Store the helper's css position
+		this.cssPosition = this.helper.css( "position" );
+		this.scrollParent = this.helper.scrollParent( true );
+		this.offsetParent = this.helper.offsetParent();
+		this.hasFixedAncestor = this.helper.parents().filter(function() {
+				return $( this ).css( "position" ) === "fixed";
+			}).length > 0;
+
+		//The element's absolute position on the page minus margins
+		this.positionAbs = this.element.offset();
+		this._refreshOffsets( event );
+
+		//Generate the original position
+		this.originalPosition = this.position = this._generatePosition( event, false );
+		this.originalPageX = event.pageX;
+		this.originalPageY = event.pageY;
+
+		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
+		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
+
+		//Set a containment if given in the options
+		this._setContainment();
+
+		//Trigger event + callbacks
+		if (this._trigger("start", event) === false) {
+			this._clear();
+			return false;
+		}
+
+		//Recache the helper size
+		this._cacheHelperProportions();
+
+		//Prepare the droppable offsets
+		if ($.ui.ddmanager && !o.dropBehaviour) {
+			$.ui.ddmanager.prepareOffsets(this, event);
+		}
+
+		// Reset helper's right/bottom css if they're set and set explicit width/height instead
+		// as this prevents resizing of elements with right/bottom set (see #7772)
+		this._normalizeRightBottom();
+
+		this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
+
+		//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
+		if ( $.ui.ddmanager ) {
+			$.ui.ddmanager.dragStart(this, event);
+		}
+
+		return true;
+	},
+
+	_refreshOffsets: function( event ) {
+		this.offset = {
+			top: this.positionAbs.top - this.margins.top,
+			left: this.positionAbs.left - this.margins.left,
+			scroll: false,
+			parent: this._getParentOffset(),
+			relative: this._getRelativeOffset()
+		};
+
+		this.offset.click = {
+			left: event.pageX - this.offset.left,
+			top: event.pageY - this.offset.top
+		};
+	},
+
+	_mouseDrag: function(event, noPropagation) {
+		// reset any necessary cached properties (see #5009)
+		if ( this.hasFixedAncestor ) {
+			this.offset.parent = this._getParentOffset();
+		}
+
+		//Compute the helpers position
+		this.position = this._generatePosition( event, true );
+		this.positionAbs = this._convertPositionTo("absolute");
+
+		//Call plugins and callbacks and use the resulting position if something is returned
+		if (!noPropagation) {
+			var ui = this._uiHash();
+			if (this._trigger("drag", event, ui) === false) {
+				this._mouseUp({});
+				return false;
+			}
+			this.position = ui.position;
+		}
+
+		this.helper[ 0 ].style.left = this.position.left + "px";
+		this.helper[ 0 ].style.top = this.position.top + "px";
+
+		if ($.ui.ddmanager) {
+			$.ui.ddmanager.drag(this, event);
+		}
+
+		return false;
+	},
+
+	_mouseStop: function(event) {
+
+		//If we are using droppables, inform the manager about the drop
+		var that = this,
+			dropped = false;
+		if ($.ui.ddmanager && !this.options.dropBehaviour) {
+			dropped = $.ui.ddmanager.drop(this, event);
+		}
+
+		//if a drop comes from outside (a sortable)
+		if (this.dropped) {
+			dropped = this.dropped;
+			this.dropped = false;
+		}
+
+		if ((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
+			$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
+				if (that._trigger("stop", event) !== false) {
+					that._clear();
+				}
+			});
+		} else {
+			if (this._trigger("stop", event) !== false) {
+				this._clear();
+			}
+		}
+
+		return false;
+	},
+
+	_mouseUp: function( event ) {
+		this._unblockFrames();
+
+		//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
+		if ( $.ui.ddmanager ) {
+			$.ui.ddmanager.dragStop(this, event);
+		}
+
+		// Only need to focus if the event occurred on the draggable itself, see #10527
+		if ( this.handleElement.is( event.target ) ) {
+			// The interaction is over; whether or not the click resulted in a drag, focus the element
+			this.element.focus();
+		}
+
+		return $.ui.mouse.prototype._mouseUp.call(this, event);
+	},
+
+	cancel: function() {
+
+		if (this.helper.is(".ui-draggable-dragging")) {
+			this._mouseUp({});
+		} else {
+			this._clear();
+		}
+
+		return this;
+
+	},
+
+	_getHandle: function(event) {
+		return this.options.handle ?
+			!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
+			true;
+	},
+
+	_setHandleClassName: function() {
+		this.handleElement = this.options.handle ?
+			this.element.find( this.options.handle ) : this.element;
+		this.handleElement.addClass( "ui-draggable-handle" );
+	},
+
+	_removeHandleClassName: function() {
+		this.handleElement.removeClass( "ui-draggable-handle" );
+	},
+
+	_createHelper: function(event) {
+
+		var o = this.options,
+			helperIsFunction = $.isFunction( o.helper ),
+			helper = helperIsFunction ?
+				$( o.helper.apply( this.element[ 0 ], [ event ] ) ) :
+				( o.helper === "clone" ?
+					this.element.clone().removeAttr( "id" ) :
+					this.element );
+
+		if (!helper.parents("body").length) {
+			helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo));
+		}
+
+		// http://bugs.jqueryui.com/ticket/9446
+		// a helper function can return the original element
+		// which wouldn't have been set to relative in _create
+		if ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) {
+			this._setPositionRelative();
+		}
+
+		if (helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) {
+			helper.css("position", "absolute");
+		}
+
+		return helper;
+
+	},
+
+	_setPositionRelative: function() {
+		if ( !( /^(?:r|a|f)/ ).test( this.element.css( "position" ) ) ) {
+			this.element[ 0 ].style.position = "relative";
+		}
+	},
+
+	_adjustOffsetFromHelper: function(obj) {
+		if (typeof obj === "string") {
+			obj = obj.split(" ");
+		}
+		if ($.isArray(obj)) {
+			obj = { left: +obj[0], top: +obj[1] || 0 };
+		}
+		if ("left" in obj) {
+			this.offset.click.left = obj.left + this.margins.left;
+		}
+		if ("right" in obj) {
+			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+		}
+		if ("top" in obj) {
+			this.offset.click.top = obj.top + this.margins.top;
+		}
+		if ("bottom" in obj) {
+			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+		}
+	},
+
+	_isRootNode: function( element ) {
+		return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ];
+	},
+
+	_getParentOffset: function() {
+
+		//Get the offsetParent and cache its position
+		var po = this.offsetParent.offset(),
+			document = this.document[ 0 ];
+
+		// This is a special case where we need to modify a offset calculated on start, since the following happened:
+		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
+		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
+		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
+		if (this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
+			po.left += this.scrollParent.scrollLeft();
+			po.top += this.scrollParent.scrollTop();
+		}
+
+		if ( this._isRootNode( this.offsetParent[ 0 ] ) ) {
+			po = { top: 0, left: 0 };
+		}
+
+		return {
+			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"), 10) || 0),
+			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"), 10) || 0)
+		};
+
+	},
+
+	_getRelativeOffset: function() {
+		if ( this.cssPosition !== "relative" ) {
+			return { top: 0, left: 0 };
+		}
+
+		var p = this.element.position(),
+			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
+
+		return {
+			top: p.top - ( parseInt(this.helper.css( "top" ), 10) || 0 ) + ( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),
+			left: p.left - ( parseInt(this.helper.css( "left" ), 10) || 0 ) + ( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )
+		};
+
+	},
+
+	_cacheMargins: function() {
+		this.margins = {
+			left: (parseInt(this.element.css("marginLeft"), 10) || 0),
+			top: (parseInt(this.element.css("marginTop"), 10) || 0),
+			right: (parseInt(this.element.css("marginRight"), 10) || 0),
+			bottom: (parseInt(this.element.css("marginBottom"), 10) || 0)
+		};
+	},
+
+	_cacheHelperProportions: function() {
+		this.helperProportions = {
+			width: this.helper.outerWidth(),
+			height: this.helper.outerHeight()
+		};
+	},
+
+	_setContainment: function() {
+
+		var isUserScrollable, c, ce,
+			o = this.options,
+			document = this.document[ 0 ];
+
+		this.relativeContainer = null;
+
+		if ( !o.containment ) {
+			this.containment = null;
+			return;
+		}
+
+		if ( o.containment === "window" ) {
+			this.containment = [
+				$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
+				$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
+				$( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left,
+				$( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
+			];
+			return;
+		}
+
+		if ( o.containment === "document") {
+			this.containment = [
+				0,
+				0,
+				$( document ).width() - this.helperProportions.width - this.margins.left,
+				( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
+			];
+			return;
+		}
+
+		if ( o.containment.constructor === Array ) {
+			this.containment = o.containment;
+			return;
+		}
+
+		if ( o.containment === "parent" ) {
+			o.containment = this.helper[ 0 ].parentNode;
+		}
+
+		c = $( o.containment );
+		ce = c[ 0 ];
+
+		if ( !ce ) {
+			return;
+		}
+
+		isUserScrollable = /(scroll|auto)/.test( c.css( "overflow" ) );
+
+		this.containment = [
+			( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
+			( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ),
+			( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -
+				( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) -
+				( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) -
+				this.helperProportions.width -
+				this.margins.left -
+				this.margins.right,
+			( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -
+				( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) -
+				( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) -
+				this.helperProportions.height -
+				this.margins.top -
+				this.margins.bottom
+		];
+		this.relativeContainer = c;
+	},
+
+	_convertPositionTo: function(d, pos) {
+
+		if (!pos) {
+			pos = this.position;
+		}
+
+		var mod = d === "absolute" ? 1 : -1,
+			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
+
+		return {
+			top: (
+				pos.top	+																// The absolute mouse position
+				this.offset.relative.top * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.top * mod -										// The offsetParent's offset without borders (offset + border)
+				( ( this.cssPosition === "fixed" ? -this.offset.scroll.top : ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod)
+			),
+			left: (
+				pos.left +																// The absolute mouse position
+				this.offset.relative.left * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.left * mod	-										// The offsetParent's offset without borders (offset + border)
+				( ( this.cssPosition === "fixed" ? -this.offset.scroll.left : ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod)
+			)
+		};
+
+	},
+
+	_generatePosition: function( event, constrainPosition ) {
+
+		var containment, co, top, left,
+			o = this.options,
+			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),
+			pageX = event.pageX,
+			pageY = event.pageY;
+
+		// Cache the scroll
+		if ( !scrollIsRootNode || !this.offset.scroll ) {
+			this.offset.scroll = {
+				top: this.scrollParent.scrollTop(),
+				left: this.scrollParent.scrollLeft()
+			};
+		}
+
+		/*
+		 * - Position constraining -
+		 * Constrain the position to a mix of grid, containment.
+		 */
+
+		// If we are not dragging yet, we won't check for options
+		if ( constrainPosition ) {
+			if ( this.containment ) {
+				if ( this.relativeContainer ){
+					co = this.relativeContainer.offset();
+					containment = [
+						this.containment[ 0 ] + co.left,
+						this.containment[ 1 ] + co.top,
+						this.containment[ 2 ] + co.left,
+						this.containment[ 3 ] + co.top
+					];
+				} else {
+					containment = this.containment;
+				}
+
+				if (event.pageX - this.offset.click.left < containment[0]) {
+					pageX = containment[0] + this.offset.click.left;
+				}
+				if (event.pageY - this.offset.click.top < containment[1]) {
+					pageY = containment[1] + this.offset.click.top;
+				}
+				if (event.pageX - this.offset.click.left > containment[2]) {
+					pageX = containment[2] + this.offset.click.left;
+				}
+				if (event.pageY - this.offset.click.top > containment[3]) {
+					pageY = containment[3] + this.offset.click.top;
+				}
+			}
+
+			if (o.grid) {
+				//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
+				top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
+				pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
+
+				left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
+				pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
+			}
+
+			if ( o.axis === "y" ) {
+				pageX = this.originalPageX;
+			}
+
+			if ( o.axis === "x" ) {
+				pageY = this.originalPageY;
+			}
+		}
+
+		return {
+			top: (
+				pageY -																	// The absolute mouse position
+				this.offset.click.top	-												// Click offset (relative to the element)
+				this.offset.relative.top -												// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.top +												// The offsetParent's offset without borders (offset + border)
+				( this.cssPosition === "fixed" ? -this.offset.scroll.top : ( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
+			),
+			left: (
+				pageX -																	// The absolute mouse position
+				this.offset.click.left -												// Click offset (relative to the element)
+				this.offset.relative.left -												// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.left +												// The offsetParent's offset without borders (offset + border)
+				( this.cssPosition === "fixed" ? -this.offset.scroll.left : ( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
+			)
+		};
+
+	},
+
+	_clear: function() {
+		this.helper.removeClass("ui-draggable-dragging");
+		if (this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) {
+			this.helper.remove();
+		}
+		this.helper = null;
+		this.cancelHelperRemoval = false;
+		if ( this.destroyOnClear ) {
+			this.destroy();
+		}
+	},
+
+	_normalizeRightBottom: function() {
+		if ( this.options.axis !== "y" && this.helper.css( "right" ) !== "auto" ) {
+			this.helper.width( this.helper.width() );
+			this.helper.css( "right", "auto" );
+		}
+		if ( this.options.axis !== "x" && this.helper.css( "bottom" ) !== "auto" ) {
+			this.helper.height( this.helper.height() );
+			this.helper.css( "bottom", "auto" );
+		}
+	},
+
+	// From now on bulk stuff - mainly helpers
+
+	_trigger: function( type, event, ui ) {
+		ui = ui || this._uiHash();
+		$.ui.plugin.call( this, type, [ event, ui, this ], true );
+
+		// Absolute position and offset (see #6884 ) have to be recalculated after plugins
+		if ( /^(drag|start|stop)/.test( type ) ) {
+			this.positionAbs = this._convertPositionTo( "absolute" );
+			ui.offset = this.positionAbs;
+		}
+		return $.Widget.prototype._trigger.call( this, type, event, ui );
+	},
+
+	plugins: {},
+
+	_uiHash: function() {
+		return {
+			helper: this.helper,
+			position: this.position,
+			originalPosition: this.originalPosition,
+			offset: this.positionAbs
+		};
+	}
+
+});
+
+$.ui.plugin.add( "draggable", "connectToSortable", {
+	start: function( event, ui, draggable ) {
+		var uiSortable = $.extend( {}, ui, {
+			item: draggable.element
+		});
+
+		draggable.sortables = [];
+		$( draggable.options.connectToSortable ).each(function() {
+			var sortable = $( this ).sortable( "instance" );
+
+			if ( sortable && !sortable.options.disabled ) {
+				draggable.sortables.push( sortable );
+
+				// refreshPositions is called at drag start to refresh the containerCache
+				// which is used in drag. This ensures it's initialized and synchronized
+				// with any changes that might have happened on the page since initialization.
+				sortable.refreshPositions();
+				sortable._trigger("activate", event, uiSortable);
+			}
+		});
+	},
+	stop: function( event, ui, draggable ) {
+		var uiSortable = $.extend( {}, ui, {
+			item: draggable.element
+		});
+
+		draggable.cancelHelperRemoval = false;
+
+		$.each( draggable.sortables, function() {
+			var sortable = this;
+
+			if ( sortable.isOver ) {
+				sortable.isOver = 0;
+
+				// Allow this sortable to handle removing the helper
+				draggable.cancelHelperRemoval = true;
+				sortable.cancelHelperRemoval = false;
+
+				// Use _storedCSS To restore properties in the sortable,
+				// as this also handles revert (#9675) since the draggable
+				// may have modified them in unexpected ways (#8809)
+				sortable._storedCSS = {
+					position: sortable.placeholder.css( "position" ),
+					top: sortable.placeholder.css( "top" ),
+					left: sortable.placeholder.css( "left" )
+				};
+
+				sortable._mouseStop(event);
+
+				// Once drag has ended, the sortable should return to using
+				// its original helper, not the shared helper from draggable
+				sortable.options.helper = sortable.options._helper;
+			} else {
+				// Prevent this Sortable from removing the helper.
+				// However, don't set the draggable to remove the helper
+				// either as another connected Sortable may yet handle the removal.
+				sortable.cancelHelperRemoval = true;
+
+				sortable._trigger( "deactivate", event, uiSortable );
+			}
+		});
+	},
+	drag: function( event, ui, draggable ) {
+		$.each( draggable.sortables, function() {
+			var innermostIntersecting = false,
+				sortable = this;
+
+			// Copy over variables that sortable's _intersectsWith uses
+			sortable.positionAbs = draggable.positionAbs;
+			sortable.helperProportions = draggable.helperProportions;
+			sortable.offset.click = draggable.offset.click;
+
+			if ( sortable._intersectsWith( sortable.containerCache ) ) {
+				innermostIntersecting = true;
+
+				$.each( draggable.sortables, function() {
+					// Copy over variables that sortable's _intersectsWith uses
+					this.positionAbs = draggable.positionAbs;
+					this.helperProportions = draggable.helperProportions;
+					this.offset.click = draggable.offset.click;
+
+					if ( this !== sortable &&
+							this._intersectsWith( this.containerCache ) &&
+							$.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) {
+						innermostIntersecting = false;
+					}
+
+					return innermostIntersecting;
+				});
+			}
+
+			if ( innermostIntersecting ) {
+				// If it intersects, we use a little isOver variable and set it once,
+				// so that the move-in stuff gets fired only once.
+				if ( !sortable.isOver ) {
+					sortable.isOver = 1;
+
+					// Store draggable's parent in case we need to reappend to it later.
+					draggable._parent = ui.helper.parent();
+
+					sortable.currentItem = ui.helper
+						.appendTo( sortable.element )
+						.data( "ui-sortable-item", true );
+
+					// Store helper option to later restore it
+					sortable.options._helper = sortable.options.helper;
+
+					sortable.options.helper = function() {
+						return ui.helper[ 0 ];
+					};
+
+					// Fire the start events of the sortable with our passed browser event,
+					// and our own helper (so it doesn't create a new one)
+					event.target = sortable.currentItem[ 0 ];
+					sortable._mouseCapture( event, true );
+					sortable._mouseStart( event, true, true );
+
+					// Because the browser event is way off the new appended portlet,
+					// modify necessary variables to reflect the changes
+					sortable.offset.click.top = draggable.offset.click.top;
+					sortable.offset.click.left = draggable.offset.click.left;
+					sortable.offset.parent.left -= draggable.offset.parent.left -
+						sortable.offset.parent.left;
+					sortable.offset.parent.top -= draggable.offset.parent.top -
+						sortable.offset.parent.top;
+
+					draggable._trigger( "toSortable", event );
+
+					// Inform draggable that the helper is in a valid drop zone,
+					// used solely in the revert option to handle "valid/invalid".
+					draggable.dropped = sortable.element;
+
+					// Need to refreshPositions of all sortables in the case that
+					// adding to one sortable changes the location of the other sortables (#9675)
+					$.each( draggable.sortables, function() {
+						this.refreshPositions();
+					});
+
+					// hack so receive/update callbacks work (mostly)
+					draggable.currentItem = draggable.element;
+					sortable.fromOutside = draggable;
+				}
+
+				if ( sortable.currentItem ) {
+					sortable._mouseDrag( event );
+					// Copy the sortable's position because the draggable's can potentially reflect
+					// a relative position, while sortable is always absolute, which the dragged
+					// element has now become. (#8809)
+					ui.position = sortable.position;
+				}
+			} else {
+				// If it doesn't intersect with the sortable, and it intersected before,
+				// we fake the drag stop of the sortable, but make sure it doesn't remove
+				// the helper by using cancelHelperRemoval.
+				if ( sortable.isOver ) {
+
+					sortable.isOver = 0;
+					sortable.cancelHelperRemoval = true;
+
+					// Calling sortable's mouseStop would trigger a revert,
+					// so revert must be temporarily false until after mouseStop is called.
+					sortable.options._revert = sortable.options.revert;
+					sortable.options.revert = false;
+
+					sortable._trigger( "out", event, sortable._uiHash( sortable ) );
+					sortable._mouseStop( event, true );
+
+					// restore sortable behaviors that were modfied
+					// when the draggable entered the sortable area (#9481)
+					sortable.options.revert = sortable.options._revert;
+					sortable.options.helper = sortable.options._helper;
+
+					if ( sortable.placeholder ) {
+						sortable.placeholder.remove();
+					}
+
+					// Restore and recalculate the draggable's offset considering the sortable
+					// may have modified them in unexpected ways. (#8809, #10669)
+					ui.helper.appendTo( draggable._parent );
+					draggable._refreshOffsets( event );
+					ui.position = draggable._generatePosition( event, true );
+
+					draggable._trigger( "fromSortable", event );
+
+					// Inform draggable that the helper is no longer in a valid drop zone
+					draggable.dropped = false;
+
+					// Need to refreshPositions of all sortables just in case removing
+					// from one sortable changes the location of other sortables (#9675)
+					$.each( draggable.sortables, function() {
+						this.refreshPositions();
+					});
+				}
+			}
+		});
+	}
+});
+
+$.ui.plugin.add("draggable", "cursor", {
+	start: function( event, ui, instance ) {
+		var t = $( "body" ),
+			o = instance.options;
+
+		if (t.css("cursor")) {
+			o._cursor = t.css("cursor");
+		}
+		t.css("cursor", o.cursor);
+	},
+	stop: function( event, ui, instance ) {
+		var o = instance.options;
+		if (o._cursor) {
+			$("body").css("cursor", o._cursor);
+		}
+	}
+});
+
+$.ui.plugin.add("draggable", "opacity", {
+	start: function( event, ui, instance ) {
+		var t = $( ui.helper ),
+			o = instance.options;
+		if (t.css("opacity")) {
+			o._opacity = t.css("opacity");
+		}
+		t.css("opacity", o.opacity);
+	},
+	stop: function( event, ui, instance ) {
+		var o = instance.options;
+		if (o._opacity) {
+			$(ui.helper).css("opacity", o._opacity);
+		}
+	}
+});
+
+$.ui.plugin.add("draggable", "scroll", {
+	start: function( event, ui, i ) {
+		if ( !i.scrollParentNotHidden ) {
+			i.scrollParentNotHidden = i.helper.scrollParent( false );
+		}
+
+		if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] && i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) {
+			i.overflowOffset = i.scrollParentNotHidden.offset();
+		}
+	},
+	drag: function( event, ui, i  ) {
+
+		var o = i.options,
+			scrolled = false,
+			scrollParent = i.scrollParentNotHidden[ 0 ],
+			document = i.document[ 0 ];
+
+		if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) {
+			if ( !o.axis || o.axis !== "x" ) {
+				if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY < o.scrollSensitivity ) {
+					scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed;
+				} else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) {
+					scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed;
+				}
+			}
+
+			if ( !o.axis || o.axis !== "y" ) {
+				if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX < o.scrollSensitivity ) {
+					scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed;
+				} else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) {
+					scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed;
+				}
+			}
+
+		} else {
+
+			if (!o.axis || o.axis !== "x") {
+				if (event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
+					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
+				} else if ($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
+					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
+				}
+			}
+
+			if (!o.axis || o.axis !== "y") {
+				if (event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
+					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
+				} else if ($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
+					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
+				}
+			}
+
+		}
+
+		if (scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
+			$.ui.ddmanager.prepareOffsets(i, event);
+		}
+
+	}
+});
+
+$.ui.plugin.add("draggable", "snap", {
+	start: function( event, ui, i ) {
+
+		var o = i.options;
+
+		i.snapElements = [];
+
+		$(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() {
+			var $t = $(this),
+				$o = $t.offset();
+			if (this !== i.element[0]) {
+				i.snapElements.push({
+					item: this,
+					width: $t.outerWidth(), height: $t.outerHeight(),
+					top: $o.top, left: $o.left
+				});
+			}
+		});
+
+	},
+	drag: function( event, ui, inst ) {
+
+		var ts, bs, ls, rs, l, r, t, b, i, first,
+			o = inst.options,
+			d = o.snapTolerance,
+			x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
+			y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
+
+		for (i = inst.snapElements.length - 1; i >= 0; i--){
+
+			l = inst.snapElements[i].left - inst.margins.left;
+			r = l + inst.snapElements[i].width;
+			t = inst.snapElements[i].top - inst.margins.top;
+			b = t + inst.snapElements[i].height;
+
+			if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) {
+				if (inst.snapElements[i].snapping) {
+					(inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
+				}
+				inst.snapElements[i].snapping = false;
+				continue;
+			}
+
+			if (o.snapMode !== "inner") {
+				ts = Math.abs(t - y2) <= d;
+				bs = Math.abs(b - y1) <= d;
+				ls = Math.abs(l - x2) <= d;
+				rs = Math.abs(r - x1) <= d;
+				if (ts) {
+					ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top;
+				}
+				if (bs) {
+					ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top;
+				}
+				if (ls) {
+					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left;
+				}
+				if (rs) {
+					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left;
+				}
+			}
+
+			first = (ts || bs || ls || rs);
+
+			if (o.snapMode !== "outer") {
+				ts = Math.abs(t - y1) <= d;
+				bs = Math.abs(b - y2) <= d;
+				ls = Math.abs(l - x1) <= d;
+				rs = Math.abs(r - x2) <= d;
+				if (ts) {
+					ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top;
+				}
+				if (bs) {
+					ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top;
+				}
+				if (ls) {
+					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left;
+				}
+				if (rs) {
+					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left;
+				}
+			}
+
+			if (!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) {
+				(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
+			}
+			inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
+
+		}
+
+	}
+});
+
+$.ui.plugin.add("draggable", "stack", {
+	start: function( event, ui, instance ) {
+		var min,
+			o = instance.options,
+			group = $.makeArray($(o.stack)).sort(function(a, b) {
+				return (parseInt($(a).css("zIndex"), 10) || 0) - (parseInt($(b).css("zIndex"), 10) || 0);
+			});
+
+		if (!group.length) { return; }
+
+		min = parseInt($(group[0]).css("zIndex"), 10) || 0;
+		$(group).each(function(i) {
+			$(this).css("zIndex", min + i);
+		});
+		this.css("zIndex", (min + group.length));
+	}
+});
+
+$.ui.plugin.add("draggable", "zIndex", {
+	start: function( event, ui, instance ) {
+		var t = $( ui.helper ),
+			o = instance.options;
+
+		if (t.css("zIndex")) {
+			o._zIndex = t.css("zIndex");
+		}
+		t.css("zIndex", o.zIndex);
+	},
+	stop: function( event, ui, instance ) {
+		var o = instance.options;
+
+		if (o._zIndex) {
+			$(ui.helper).css("zIndex", o._zIndex);
+		}
+	}
+});
+
+var draggable = $.ui.draggable;
+
+
+/*!
+ * jQuery UI Droppable 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/droppable/
+ */
+
+
+$.widget( "ui.droppable", {
+	version: "1.11.4",
+	widgetEventPrefix: "drop",
+	options: {
+		accept: "*",
+		activeClass: false,
+		addClasses: true,
+		greedy: false,
+		hoverClass: false,
+		scope: "default",
+		tolerance: "intersect",
+
+		// callbacks
+		activate: null,
+		deactivate: null,
+		drop: null,
+		out: null,
+		over: null
+	},
+	_create: function() {
+
+		var proportions,
+			o = this.options,
+			accept = o.accept;
+
+		this.isover = false;
+		this.isout = true;
+
+		this.accept = $.isFunction( accept ) ? accept : function( d ) {
+			return d.is( accept );
+		};
+
+		this.proportions = function( /* valueToWrite */ ) {
+			if ( arguments.length ) {
+				// Store the droppable's proportions
+				proportions = arguments[ 0 ];
+			} else {
+				// Retrieve or derive the droppable's proportions
+				return proportions ?
+					proportions :
+					proportions = {
+						width: this.element[ 0 ].offsetWidth,
+						height: this.element[ 0 ].offsetHeight
+					};
+			}
+		};
+
+		this._addToManager( o.scope );
+
+		o.addClasses && this.element.addClass( "ui-droppable" );
+
+	},
+
+	_addToManager: function( scope ) {
+		// Add the reference and positions to the manager
+		$.ui.ddmanager.droppables[ scope ] = $.ui.ddmanager.droppables[ scope ] || [];
+		$.ui.ddmanager.droppables[ scope ].push( this );
+	},
+
+	_splice: function( drop ) {
+		var i = 0;
+		for ( ; i < drop.length; i++ ) {
+			if ( drop[ i ] === this ) {
+				drop.splice( i, 1 );
+			}
+		}
+	},
+
+	_destroy: function() {
+		var drop = $.ui.ddmanager.droppables[ this.options.scope ];
+
+		this._splice( drop );
+
+		this.element.removeClass( "ui-droppable ui-droppable-disabled" );
+	},
+
+	_setOption: function( key, value ) {
+
+		if ( key === "accept" ) {
+			this.accept = $.isFunction( value ) ? value : function( d ) {
+				return d.is( value );
+			};
+		} else if ( key === "scope" ) {
+			var drop = $.ui.ddmanager.droppables[ this.options.scope ];
+
+			this._splice( drop );
+			this._addToManager( value );
+		}
+
+		this._super( key, value );
+	},
+
+	_activate: function( event ) {
+		var draggable = $.ui.ddmanager.current;
+		if ( this.options.activeClass ) {
+			this.element.addClass( this.options.activeClass );
+		}
+		if ( draggable ){
+			this._trigger( "activate", event, this.ui( draggable ) );
+		}
+	},
+
+	_deactivate: function( event ) {
+		var draggable = $.ui.ddmanager.current;
+		if ( this.options.activeClass ) {
+			this.element.removeClass( this.options.activeClass );
+		}
+		if ( draggable ){
+			this._trigger( "deactivate", event, this.ui( draggable ) );
+		}
+	},
+
+	_over: function( event ) {
+
+		var draggable = $.ui.ddmanager.current;
+
+		// Bail if draggable and droppable are same element
+		if ( !draggable || ( draggable.currentItem || draggable.element )[ 0 ] === this.element[ 0 ] ) {
+			return;
+		}
+
+		if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || draggable.element ) ) ) {
+			if ( this.options.hoverClass ) {
+				this.element.addClass( this.options.hoverClass );
+			}
+			this._trigger( "over", event, this.ui( draggable ) );
+		}
+
+	},
+
+	_out: function( event ) {
+
+		var draggable = $.ui.ddmanager.current;
+
+		// Bail if draggable and droppable are same element
+		if ( !draggable || ( draggable.currentItem || draggable.element )[ 0 ] === this.element[ 0 ] ) {
+			return;
+		}
+
+		if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || draggable.element ) ) ) {
+			if ( this.options.hoverClass ) {
+				this.element.removeClass( this.options.hoverClass );
+			}
+			this._trigger( "out", event, this.ui( draggable ) );
+		}
+
+	},
+
+	_drop: function( event, custom ) {
+
+		var draggable = custom || $.ui.ddmanager.current,
+			childrenIntersection = false;
+
+		// Bail if draggable and droppable are same element
+		if ( !draggable || ( draggable.currentItem || draggable.element )[ 0 ] === this.element[ 0 ] ) {
+			return false;
+		}
+
+		this.element.find( ":data(ui-droppable)" ).not( ".ui-draggable-dragging" ).each(function() {
+			var inst = $( this ).droppable( "instance" );
+			if (
+				inst.options.greedy &&
+				!inst.options.disabled &&
+				inst.options.scope === draggable.options.scope &&
+				inst.accept.call( inst.element[ 0 ], ( draggable.currentItem || draggable.element ) ) &&
+				$.ui.intersect( draggable, $.extend( inst, { offset: inst.element.offset() } ), inst.options.tolerance, event )
+			) { childrenIntersection = true; return false; }
+		});
+		if ( childrenIntersection ) {
+			return false;
+		}
+
+		if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || draggable.element ) ) ) {
+			if ( this.options.activeClass ) {
+				this.element.removeClass( this.options.activeClass );
+			}
+			if ( this.options.hoverClass ) {
+				this.element.removeClass( this.options.hoverClass );
+			}
+			this._trigger( "drop", event, this.ui( draggable ) );
+			return this.element;
+		}
+
+		return false;
+
+	},
+
+	ui: function( c ) {
+		return {
+			draggable: ( c.currentItem || c.element ),
+			helper: c.helper,
+			position: c.position,
+			offset: c.positionAbs
+		};
+	}
+
+});
+
+$.ui.intersect = (function() {
+	function isOverAxis( x, reference, size ) {
+		return ( x >= reference ) && ( x < ( reference + size ) );
+	}
+
+	return function( draggable, droppable, toleranceMode, event ) {
+
+		if ( !droppable.offset ) {
+			return false;
+		}
+
+		var x1 = ( draggable.positionAbs || draggable.position.absolute ).left + draggable.margins.left,
+			y1 = ( draggable.positionAbs || draggable.position.absolute ).top + draggable.margins.top,
+			x2 = x1 + draggable.helperProportions.width,
+			y2 = y1 + draggable.helperProportions.height,
+			l = droppable.offset.left,
+			t = droppable.offset.top,
+			r = l + droppable.proportions().width,
+			b = t + droppable.proportions().height;
+
+		switch ( toleranceMode ) {
+		case "fit":
+			return ( l <= x1 && x2 <= r && t <= y1 && y2 <= b );
+		case "intersect":
+			return ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half
+				x2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half
+				t < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half
+				y2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half
+		case "pointer":
+			return isOverAxis( event.pageY, t, droppable.proportions().height ) && isOverAxis( event.pageX, l, droppable.proportions().width );
+		case "touch":
+			return (
+				( y1 >= t && y1 <= b ) || // Top edge touching
+				( y2 >= t && y2 <= b ) || // Bottom edge touching
+				( y1 < t && y2 > b ) // Surrounded vertically
+			) && (
+				( x1 >= l && x1 <= r ) || // Left edge touching
+				( x2 >= l && x2 <= r ) || // Right edge touching
+				( x1 < l && x2 > r ) // Surrounded horizontally
+			);
+		default:
+			return false;
+		}
+	};
+})();
+
+/*
+	This manager tracks offsets of draggables and droppables
+*/
+$.ui.ddmanager = {
+	current: null,
+	droppables: { "default": [] },
+	prepareOffsets: function( t, event ) {
+
+		var i, j,
+			m = $.ui.ddmanager.droppables[ t.options.scope ] || [],
+			type = event ? event.type : null, // workaround for #2317
+			list = ( t.currentItem || t.element ).find( ":data(ui-droppable)" ).addBack();
+
+		droppablesLoop: for ( i = 0; i < m.length; i++ ) {
+
+			// No disabled and non-accepted
+			if ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ], ( t.currentItem || t.element ) ) ) ) {
+				continue;
+			}
+
+			// Filter out elements in the current dragged item
+			for ( j = 0; j < list.length; j++ ) {
+				if ( list[ j ] === m[ i ].element[ 0 ] ) {
+					m[ i ].proportions().height = 0;
+					continue droppablesLoop;
+				}
+			}
+
+			m[ i ].visible = m[ i ].element.css( "display" ) !== "none";
+			if ( !m[ i ].visible ) {
+				continue;
+			}
+
+			// Activate the droppable if used directly from draggables
+			if ( type === "mousedown" ) {
+				m[ i ]._activate.call( m[ i ], event );
+			}
+
+			m[ i ].offset = m[ i ].element.offset();
+			m[ i ].proportions({ width: m[ i ].element[ 0 ].offsetWidth, height: m[ i ].element[ 0 ].offsetHeight });
+
+		}
+
+	},
+	drop: function( draggable, event ) {
+
+		var dropped = false;
+		// Create a copy of the droppables in case the list changes during the drop (#9116)
+		$.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() {
+
+			if ( !this.options ) {
+				return;
+			}
+			if ( !this.options.disabled && this.visible && $.ui.intersect( draggable, this, this.options.tolerance, event ) ) {
+				dropped = this._drop.call( this, event ) || dropped;
+			}
+
+			if ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ], ( draggable.currentItem || draggable.element ) ) ) {
+				this.isout = true;
+				this.isover = false;
+				this._deactivate.call( this, event );
+			}
+
+		});
+		return dropped;
+
+	},
+	dragStart: function( draggable, event ) {
+		// Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
+		draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
+			if ( !draggable.options.refreshPositions ) {
+				$.ui.ddmanager.prepareOffsets( draggable, event );
+			}
+		});
+	},
+	drag: function( draggable, event ) {
+
+		// If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
+		if ( draggable.options.refreshPositions ) {
+			$.ui.ddmanager.prepareOffsets( draggable, event );
+		}
+
+		// Run through all droppables and check their positions based on specific tolerance options
+		$.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() {
+
+			if ( this.options.disabled || this.greedyChild || !this.visible ) {
+				return;
+			}
+
+			var parentInstance, scope, parent,
+				intersects = $.ui.intersect( draggable, this, this.options.tolerance, event ),
+				c = !intersects && this.isover ? "isout" : ( intersects && !this.isover ? "isover" : null );
+			if ( !c ) {
+				return;
+			}
+
+			if ( this.options.greedy ) {
+				// find droppable parents with same scope
+				scope = this.options.scope;
+				parent = this.element.parents( ":data(ui-droppable)" ).filter(function() {
+					return $( this ).droppable( "instance" ).options.scope === scope;
+				});
+
+				if ( parent.length ) {
+					parentInstance = $( parent[ 0 ] ).droppable( "instance" );
+					parentInstance.greedyChild = ( c === "isover" );
+				}
+			}
+
+			// we just moved into a greedy child
+			if ( parentInstance && c === "isover" ) {
+				parentInstance.isover = false;
+				parentInstance.isout = true;
+				parentInstance._out.call( parentInstance, event );
+			}
+
+			this[ c ] = true;
+			this[c === "isout" ? "isover" : "isout"] = false;
+			this[c === "isover" ? "_over" : "_out"].call( this, event );
+
+			// we just moved out of a greedy child
+			if ( parentInstance && c === "isout" ) {
+				parentInstance.isout = false;
+				parentInstance.isover = true;
+				parentInstance._over.call( parentInstance, event );
+			}
+		});
+
+	},
+	dragStop: function( draggable, event ) {
+		draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
+		// Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
+		if ( !draggable.options.refreshPositions ) {
+			$.ui.ddmanager.prepareOffsets( draggable, event );
+		}
+	}
+};
+
+var droppable = $.ui.droppable;
+
+
+/*!
+ * jQuery UI Resizable 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/resizable/
+ */
+
+
+$.widget("ui.resizable", $.ui.mouse, {
+	version: "1.11.4",
+	widgetEventPrefix: "resize",
+	options: {
+		alsoResize: false,
+		animate: false,
+		animateDuration: "slow",
+		animateEasing: "swing",
+		aspectRatio: false,
+		autoHide: false,
+		containment: false,
+		ghost: false,
+		grid: false,
+		handles: "e,s,se",
+		helper: false,
+		maxHeight: null,
+		maxWidth: null,
+		minHeight: 10,
+		minWidth: 10,
+		// See #7960
+		zIndex: 90,
+
+		// callbacks
+		resize: null,
+		start: null,
+		stop: null
+	},
+
+	_num: function( value ) {
+		return parseInt( value, 10 ) || 0;
+	},
+
+	_isNumber: function( value ) {
+		return !isNaN( parseInt( value, 10 ) );
+	},
+
+	_hasScroll: function( el, a ) {
+
+		if ( $( el ).css( "overflow" ) === "hidden") {
+			return false;
+		}
+
+		var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
+			has = false;
+
+		if ( el[ scroll ] > 0 ) {
+			return true;
+		}
+
+		// TODO: determine which cases actually cause this to happen
+		// if the element doesn't have the scroll set, see if it's possible to
+		// set the scroll
+		el[ scroll ] = 1;
+		has = ( el[ scroll ] > 0 );
+		el[ scroll ] = 0;
+		return has;
+	},
+
+	_create: function() {
+
+		var n, i, handle, axis, hname,
+			that = this,
+			o = this.options;
+		this.element.addClass("ui-resizable");
+
+		$.extend(this, {
+			_aspectRatio: !!(o.aspectRatio),
+			aspectRatio: o.aspectRatio,
+			originalElement: this.element,
+			_proportionallyResizeElements: [],
+			_helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null
+		});
+
+		// Wrap the element if it cannot hold child nodes
+		if (this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)) {
+
+			this.element.wrap(
+				$("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({
+					position: this.element.css("position"),
+					width: this.element.outerWidth(),
+					height: this.element.outerHeight(),
+					top: this.element.css("top"),
+					left: this.element.css("left")
+				})
+			);
+
+			this.element = this.element.parent().data(
+				"ui-resizable", this.element.resizable( "instance" )
+			);
+
+			this.elementIsWrapper = true;
+
+			this.element.css({
+				marginLeft: this.originalElement.css("marginLeft"),
+				marginTop: this.originalElement.css("marginTop"),
+				marginRight: this.originalElement.css("marginRight"),
+				marginBottom: this.originalElement.css("marginBottom")
+			});
+			this.originalElement.css({
+				marginLeft: 0,
+				marginTop: 0,
+				marginRight: 0,
+				marginBottom: 0
+			});
+			// support: Safari
+			// Prevent Safari textarea resize
+			this.originalResizeStyle = this.originalElement.css("resize");
+			this.originalElement.css("resize", "none");
+
+			this._proportionallyResizeElements.push( this.originalElement.css({
+				position: "static",
+				zoom: 1,
+				display: "block"
+			}) );
+
+			// support: IE9
+			// avoid IE jump (hard set the margin)
+			this.originalElement.css({ margin: this.originalElement.css("margin") });
+
+			this._proportionallyResize();
+		}
+
+		this.handles = o.handles ||
+			( !$(".ui-resizable-handle", this.element).length ?
+				"e,s,se" : {
+					n: ".ui-resizable-n",
+					e: ".ui-resizable-e",
+					s: ".ui-resizable-s",
+					w: ".ui-resizable-w",
+					se: ".ui-resizable-se",
+					sw: ".ui-resizable-sw",
+					ne: ".ui-resizable-ne",
+					nw: ".ui-resizable-nw"
+				} );
+
+		this._handles = $();
+		if ( this.handles.constructor === String ) {
+
+			if ( this.handles === "all") {
+				this.handles = "n,e,s,w,se,sw,ne,nw";
+			}
+
+			n = this.handles.split(",");
+			this.handles = {};
+
+			for (i = 0; i < n.length; i++) {
+
+				handle = $.trim(n[i]);
+				hname = "ui-resizable-" + handle;
+				axis = $("<div class='ui-resizable-handle " + hname + "'></div>");
+
+				axis.css({ zIndex: o.zIndex });
+
+				// TODO : What's going on here?
+				if ("se" === handle) {
+					axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se");
+				}
+
+				this.handles[handle] = ".ui-resizable-" + handle;
+				this.element.append(axis);
+			}
+
+		}
+
+		this._renderAxis = function(target) {
+
+			var i, axis, padPos, padWrapper;
+
+			target = target || this.element;
+
+			for (i in this.handles) {
+
+				if (this.handles[i].constructor === String) {
+					this.handles[i] = this.element.children( this.handles[ i ] ).first().show();
+				} else if ( this.handles[ i ].jquery || this.handles[ i ].nodeType ) {
+					this.handles[ i ] = $( this.handles[ i ] );
+					this._on( this.handles[ i ], { "mousedown": that._mouseDown });
+				}
+
+				if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)) {
+
+					axis = $(this.handles[i], this.element);
+
+					padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();
+
+					padPos = [ "padding",
+						/ne|nw|n/.test(i) ? "Top" :
+						/se|sw|s/.test(i) ? "Bottom" :
+						/^e$/.test(i) ? "Right" : "Left" ].join("");
+
+					target.css(padPos, padWrapper);
+
+					this._proportionallyResize();
+				}
+
+				this._handles = this._handles.add( this.handles[ i ] );
+			}
+		};
+
+		// TODO: make renderAxis a prototype function
+		this._renderAxis(this.element);
+
+		this._handles = this._handles.add( this.element.find( ".ui-resizable-handle" ) );
+		this._handles.disableSelection();
+
+		this._handles.mouseover(function() {
+			if (!that.resizing) {
+				if (this.className) {
+					axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
+				}
+				that.axis = axis && axis[1] ? axis[1] : "se";
+			}
+		});
+
+		if (o.autoHide) {
+			this._handles.hide();
+			$(this.element)
+				.addClass("ui-resizable-autohide")
+				.mouseenter(function() {
+					if (o.disabled) {
+						return;
+					}
+					$(this).removeClass("ui-resizable-autohide");
+					that._handles.show();
+				})
+				.mouseleave(function() {
+					if (o.disabled) {
+						return;
+					}
+					if (!that.resizing) {
+						$(this).addClass("ui-resizable-autohide");
+						that._handles.hide();
+					}
+				});
+		}
+
+		this._mouseInit();
+	},
+
+	_destroy: function() {
+
+		this._mouseDestroy();
+
+		var wrapper,
+			_destroy = function(exp) {
+				$(exp)
+					.removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
+					.removeData("resizable")
+					.removeData("ui-resizable")
+					.unbind(".resizable")
+					.find(".ui-resizable-handle")
+						.remove();
+			};
+
+		// TODO: Unwrap at same DOM position
+		if (this.elementIsWrapper) {
+			_destroy(this.element);
+			wrapper = this.element;
+			this.originalElement.css({
+				position: wrapper.css("position"),
+				width: wrapper.outerWidth(),
+				height: wrapper.outerHeight(),
+				top: wrapper.css("top"),
+				left: wrapper.css("left")
+			}).insertAfter( wrapper );
+			wrapper.remove();
+		}
+
+		this.originalElement.css("resize", this.originalResizeStyle);
+		_destroy(this.originalElement);
+
+		return this;
+	},
+
+	_mouseCapture: function(event) {
+		var i, handle,
+			capture = false;
+
+		for (i in this.handles) {
+			handle = $(this.handles[i])[0];
+			if (handle === event.target || $.contains(handle, event.target)) {
+				capture = true;
+			}
+		}
+
+		return !this.options.disabled && capture;
+	},
+
+	_mouseStart: function(event) {
+
+		var curleft, curtop, cursor,
+			o = this.options,
+			el = this.element;
+
+		this.resizing = true;
+
+		this._renderProxy();
+
+		curleft = this._num(this.helper.css("left"));
+		curtop = this._num(this.helper.css("top"));
+
+		if (o.containment) {
+			curleft += $(o.containment).scrollLeft() || 0;
+			curtop += $(o.containment).scrollTop() || 0;
+		}
+
+		this.offset = this.helper.offset();
+		this.position = { left: curleft, top: curtop };
+
+		this.size = this._helper ? {
+				width: this.helper.width(),
+				height: this.helper.height()
+			} : {
+				width: el.width(),
+				height: el.height()
+			};
+
+		this.originalSize = this._helper ? {
+				width: el.outerWidth(),
+				height: el.outerHeight()
+			} : {
+				width: el.width(),
+				height: el.height()
+			};
+
+		this.sizeDiff = {
+			width: el.outerWidth() - el.width(),
+			height: el.outerHeight() - el.height()
+		};
+
+		this.originalPosition = { left: curleft, top: curtop };
+		this.originalMousePosition = { left: event.pageX, top: event.pageY };
+
+		this.aspectRatio = (typeof o.aspectRatio === "number") ?
+			o.aspectRatio :
+			((this.originalSize.width / this.originalSize.height) || 1);
+
+		cursor = $(".ui-resizable-" + this.axis).css("cursor");
+		$("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor);
+
+		el.addClass("ui-resizable-resizing");
+		this._propagate("start", event);
+		return true;
+	},
+
+	_mouseDrag: function(event) {
+
+		var data, props,
+			smp = this.originalMousePosition,
+			a = this.axis,
+			dx = (event.pageX - smp.left) || 0,
+			dy = (event.pageY - smp.top) || 0,
+			trigger = this._change[a];
+
+		this._updatePrevProperties();
+
+		if (!trigger) {
+			return false;
+		}
+
+		data = trigger.apply(this, [ event, dx, dy ]);
+
+		this._updateVirtualBoundaries(event.shiftKey);
+		if (this._aspectRatio || event.shiftKey) {
+			data = this._updateRatio(data, event);
+		}
+
+		data = this._respectSize(data, event);
+
+		this._updateCache(data);
+
+		this._propagate("resize", event);
+
+		props = this._applyChanges();
+
+		if ( !this._helper && this._proportionallyResizeElements.length ) {
+			this._proportionallyResize();
+		}
+
+		if ( !$.isEmptyObject( props ) ) {
+			this._updatePrevProperties();
+			this._trigger( "resize", event, this.ui() );
+			this._applyChanges();
+		}
+
+		return false;
+	},
+
+	_mouseStop: function(event) {
+
+		this.resizing = false;
+		var pr, ista, soffseth, soffsetw, s, left, top,
+			o = this.options, that = this;
+
+		if (this._helper) {
+
+			pr = this._proportionallyResizeElements;
+			ista = pr.length && (/textarea/i).test(pr[0].nodeName);
+			soffseth = ista && this._hasScroll(pr[0], "left") ? 0 : that.sizeDiff.height;
+			soffsetw = ista ? 0 : that.sizeDiff.width;
+
+			s = {
+				width: (that.helper.width()  - soffsetw),
+				height: (that.helper.height() - soffseth)
+			};
+			left = (parseInt(that.element.css("left"), 10) +
+				(that.position.left - that.originalPosition.left)) || null;
+			top = (parseInt(that.element.css("top"), 10) +
+				(that.position.top - that.originalPosition.top)) || null;
+
+			if (!o.animate) {
+				this.element.css($.extend(s, { top: top, left: left }));
+			}
+
+			that.helper.height(that.size.height);
+			that.helper.width(that.size.width);
+
+			if (this._helper && !o.animate) {
+				this._proportionallyResize();
+			}
+		}
+
+		$("body").css("cursor", "auto");
+
+		this.element.removeClass("ui-resizable-resizing");
+
+		this._propagate("stop", event);
+
+		if (this._helper) {
+			this.helper.remove();
+		}
+
+		return false;
+
+	},
+
+	_updatePrevProperties: function() {
+		this.prevPosition = {
+			top: this.position.top,
+			left: this.position.left
+		};
+		this.prevSize = {
+			width: this.size.width,
+			height: this.size.height
+		};
+	},
+
+	_applyChanges: function() {
+		var props = {};
+
+		if ( this.position.top !== this.prevPosition.top ) {
+			props.top = this.position.top + "px";
+		}
+		if ( this.position.left !== this.prevPosition.left ) {
+			props.left = this.position.left + "px";
+		}
+		if ( this.size.width !== this.prevSize.width ) {
+			props.width = this.size.width + "px";
+		}
+		if ( this.size.height !== this.prevSize.height ) {
+			props.height = this.size.height + "px";
+		}
+
+		this.helper.css( props );
+
+		return props;
+	},
+
+	_updateVirtualBoundaries: function(forceAspectRatio) {
+		var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b,
+			o = this.options;
+
+		b = {
+			minWidth: this._isNumber(o.minWidth) ? o.minWidth : 0,
+			maxWidth: this._isNumber(o.maxWidth) ? o.maxWidth : Infinity,
+			minHeight: this._isNumber(o.minHeight) ? o.minHeight : 0,
+			maxHeight: this._isNumber(o.maxHeight) ? o.maxHeight : Infinity
+		};
+
+		if (this._aspectRatio || forceAspectRatio) {
+			pMinWidth = b.minHeight * this.aspectRatio;
+			pMinHeight = b.minWidth / this.aspectRatio;
+			pMaxWidth = b.maxHeight * this.aspectRatio;
+			pMaxHeight = b.maxWidth / this.aspectRatio;
+
+			if (pMinWidth > b.minWidth) {
+				b.minWidth = pMinWidth;
+			}
+			if (pMinHeight > b.minHeight) {
+				b.minHeight = pMinHeight;
+			}
+			if (pMaxWidth < b.maxWidth) {
+				b.maxWidth = pMaxWidth;
+			}
+			if (pMaxHeight < b.maxHeight) {
+				b.maxHeight = pMaxHeight;
+			}
+		}
+		this._vBoundaries = b;
+	},
+
+	_updateCache: function(data) {
+		this.offset = this.helper.offset();
+		if (this._isNumber(data.left)) {
+			this.position.left = data.left;
+		}
+		if (this._isNumber(data.top)) {
+			this.position.top = data.top;
+		}
+		if (this._isNumber(data.height)) {
+			this.size.height = data.height;
+		}
+		if (this._isNumber(data.width)) {
+			this.size.width = data.width;
+		}
+	},
+
+	_updateRatio: function( data ) {
+
+		var cpos = this.position,
+			csize = this.size,
+			a = this.axis;
+
+		if (this._isNumber(data.height)) {
+			data.width = (data.height * this.aspectRatio);
+		} else if (this._isNumber(data.width)) {
+			data.height = (data.width / this.aspectRatio);
+		}
+
+		if (a === "sw") {
+			data.left = cpos.left + (csize.width - data.width);
+			data.top = null;
+		}
+		if (a === "nw") {
+			data.top = cpos.top + (csize.height - data.height);
+			data.left = cpos.left + (csize.width - data.width);
+		}
+
+		return data;
+	},
+
+	_respectSize: function( data ) {
+
+		var o = this._vBoundaries,
+			a = this.axis,
+			ismaxw = this._isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width),
+			ismaxh = this._isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
+			isminw = this._isNumber(data.width) && o.minWidth && (o.minWidth > data.width),
+			isminh = this._isNumber(data.height) && o.minHeight && (o.minHeight > data.height),
+			dw = this.originalPosition.left + this.originalSize.width,
+			dh = this.position.top + this.size.height,
+			cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
+		if (isminw) {
+			data.width = o.minWidth;
+		}
+		if (isminh) {
+			data.height = o.minHeight;
+		}
+		if (ismaxw) {
+			data.width = o.maxWidth;
+		}
+		if (ismaxh) {
+			data.height = o.maxHeight;
+		}
+
+		if (isminw && cw) {
+			data.left = dw - o.minWidth;
+		}
+		if (ismaxw && cw) {
+			data.left = dw - o.maxWidth;
+		}
+		if (isminh && ch) {
+			data.top = dh - o.minHeight;
+		}
+		if (ismaxh && ch) {
+			data.top = dh - o.maxHeight;
+		}
+
+		// Fixing jump error on top/left - bug #2330
+		if (!data.width && !data.height && !data.left && data.top) {
+			data.top = null;
+		} else if (!data.width && !data.height && !data.top && data.left) {
+			data.left = null;
+		}
+
+		return data;
+	},
+
+	_getPaddingPlusBorderDimensions: function( element ) {
+		var i = 0,
+			widths = [],
+			borders = [
+				element.css( "borderTopWidth" ),
+				element.css( "borderRightWidth" ),
+				element.css( "borderBottomWidth" ),
+				element.css( "borderLeftWidth" )
+			],
+			paddings = [
+				element.css( "paddingTop" ),
+				element.css( "paddingRight" ),
+				element.css( "paddingBottom" ),
+				element.css( "paddingLeft" )
+			];
+
+		for ( ; i < 4; i++ ) {
+			widths[ i ] = ( parseInt( borders[ i ], 10 ) || 0 );
+			widths[ i ] += ( parseInt( paddings[ i ], 10 ) || 0 );
+		}
+
+		return {
+			height: widths[ 0 ] + widths[ 2 ],
+			width: widths[ 1 ] + widths[ 3 ]
+		};
+	},
+
+	_proportionallyResize: function() {
+
+		if (!this._proportionallyResizeElements.length) {
+			return;
+		}
+
+		var prel,
+			i = 0,
+			element = this.helper || this.element;
+
+		for ( ; i < this._proportionallyResizeElements.length; i++) {
+
+			prel = this._proportionallyResizeElements[i];
+
+			// TODO: Seems like a bug to cache this.outerDimensions
+			// considering that we are in a loop.
+			if (!this.outerDimensions) {
+				this.outerDimensions = this._getPaddingPlusBorderDimensions( prel );
+			}
+
+			prel.css({
+				height: (element.height() - this.outerDimensions.height) || 0,
+				width: (element.width() - this.outerDimensions.width) || 0
+			});
+
+		}
+
+	},
+
+	_renderProxy: function() {
+
+		var el = this.element, o = this.options;
+		this.elementOffset = el.offset();
+
+		if (this._helper) {
+
+			this.helper = this.helper || $("<div style='overflow:hidden;'></div>");
+
+			this.helper.addClass(this._helper).css({
+				width: this.element.outerWidth() - 1,
+				height: this.element.outerHeight() - 1,
+				position: "absolute",
+				left: this.elementOffset.left + "px",
+				top: this.elementOffset.top + "px",
+				zIndex: ++o.zIndex //TODO: Don't modify option
+			});
+
+			this.helper
+				.appendTo("body")
+				.disableSelection();
+
+		} else {
+			this.helper = this.element;
+		}
+
+	},
+
+	_change: {
+		e: function(event, dx) {
+			return { width: this.originalSize.width + dx };
+		},
+		w: function(event, dx) {
+			var cs = this.originalSize, sp = this.originalPosition;
+			return { left: sp.left + dx, width: cs.width - dx };
+		},
+		n: function(event, dx, dy) {
+			var cs = this.originalSize, sp = this.originalPosition;
+			return { top: sp.top + dy, height: cs.height - dy };
+		},
+		s: function(event, dx, dy) {
+			return { height: this.originalSize.height + dy };
+		},
+		se: function(event, dx, dy) {
+			return $.extend(this._change.s.apply(this, arguments),
+				this._change.e.apply(this, [ event, dx, dy ]));
+		},
+		sw: function(event, dx, dy) {
+			return $.extend(this._change.s.apply(this, arguments),
+				this._change.w.apply(this, [ event, dx, dy ]));
+		},
+		ne: function(event, dx, dy) {
+			return $.extend(this._change.n.apply(this, arguments),
+				this._change.e.apply(this, [ event, dx, dy ]));
+		},
+		nw: function(event, dx, dy) {
+			return $.extend(this._change.n.apply(this, arguments),
+				this._change.w.apply(this, [ event, dx, dy ]));
+		}
+	},
+
+	_propagate: function(n, event) {
+		$.ui.plugin.call(this, n, [ event, this.ui() ]);
+		(n !== "resize" && this._trigger(n, event, this.ui()));
+	},
+
+	plugins: {},
+
+	ui: function() {
+		return {
+			originalElement: this.originalElement,
+			element: this.element,
+			helper: this.helper,
+			position: this.position,
+			size: this.size,
+			originalSize: this.originalSize,
+			originalPosition: this.originalPosition
+		};
+	}
+
+});
+
+/*
+ * Resizable Extensions
+ */
+
+$.ui.plugin.add("resizable", "animate", {
+
+	stop: function( event ) {
+		var that = $(this).resizable( "instance" ),
+			o = that.options,
+			pr = that._proportionallyResizeElements,
+			ista = pr.length && (/textarea/i).test(pr[0].nodeName),
+			soffseth = ista && that._hasScroll(pr[0], "left") ? 0 : that.sizeDiff.height,
+			soffsetw = ista ? 0 : that.sizeDiff.width,
+			style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) },
+			left = (parseInt(that.element.css("left"), 10) +
+				(that.position.left - that.originalPosition.left)) || null,
+			top = (parseInt(that.element.css("top"), 10) +
+				(that.position.top - that.originalPosition.top)) || null;
+
+		that.element.animate(
+			$.extend(style, top && left ? { top: top, left: left } : {}), {
+				duration: o.animateDuration,
+				easing: o.animateEasing,
+				step: function() {
+
+					var data = {
+						width: parseInt(that.element.css("width"), 10),
+						height: parseInt(that.element.css("height"), 10),
+						top: parseInt(that.element.css("top"), 10),
+						left: parseInt(that.element.css("left"), 10)
+					};
+
+					if (pr && pr.length) {
+						$(pr[0]).css({ width: data.width, height: data.height });
+					}
+
+					// propagating resize, and updating values for each animation step
+					that._updateCache(data);
+					that._propagate("resize", event);
+
+				}
+			}
+		);
+	}
+
+});
+
+$.ui.plugin.add( "resizable", "containment", {
+
+	start: function() {
+		var element, p, co, ch, cw, width, height,
+			that = $( this ).resizable( "instance" ),
+			o = that.options,
+			el = that.element,
+			oc = o.containment,
+			ce = ( oc instanceof $ ) ? oc.get( 0 ) : ( /parent/.test( oc ) ) ? el.parent().get( 0 ) : oc;
+
+		if ( !ce ) {
+			return;
+		}
+
+		that.containerElement = $( ce );
+
+		if ( /document/.test( oc ) || oc === document ) {
+			that.containerOffset = {
+				left: 0,
+				top: 0
+			};
+			that.containerPosition = {
+				left: 0,
+				top: 0
+			};
+
+			that.parentData = {
+				element: $( document ),
+				left: 0,
+				top: 0,
+				width: $( document ).width(),
+				height: $( document ).height() || document.body.parentNode.scrollHeight
+			};
+		} else {
+			element = $( ce );
+			p = [];
+			$([ "Top", "Right", "Left", "Bottom" ]).each(function( i, name ) {
+				p[ i ] = that._num( element.css( "padding" + name ) );
+			});
+
+			that.containerOffset = element.offset();
+			that.containerPosition = element.position();
+			that.containerSize = {
+				height: ( element.innerHeight() - p[ 3 ] ),
+				width: ( element.innerWidth() - p[ 1 ] )
+			};
+
+			co = that.containerOffset;
+			ch = that.containerSize.height;
+			cw = that.containerSize.width;
+			width = ( that._hasScroll ( ce, "left" ) ? ce.scrollWidth : cw );
+			height = ( that._hasScroll ( ce ) ? ce.scrollHeight : ch ) ;
+
+			that.parentData = {
+				element: ce,
+				left: co.left,
+				top: co.top,
+				width: width,
+				height: height
+			};
+		}
+	},
+
+	resize: function( event ) {
+		var woset, hoset, isParent, isOffsetRelative,
+			that = $( this ).resizable( "instance" ),
+			o = that.options,
+			co = that.containerOffset,
+			cp = that.position,
+			pRatio = that._aspectRatio || event.shiftKey,
+			cop = {
+				top: 0,
+				left: 0
+			},
+			ce = that.containerElement,
+			continueResize = true;
+
+		if ( ce[ 0 ] !== document && ( /static/ ).test( ce.css( "position" ) ) ) {
+			cop = co;
+		}
+
+		if ( cp.left < ( that._helper ? co.left : 0 ) ) {
+			that.size.width = that.size.width +
+				( that._helper ?
+					( that.position.left - co.left ) :
+					( that.position.left - cop.left ) );
+
+			if ( pRatio ) {
+				that.size.height = that.size.width / that.aspectRatio;
+				continueResize = false;
+			}
+			that.position.left = o.helper ? co.left : 0;
+		}
+
+		if ( cp.top < ( that._helper ? co.top : 0 ) ) {
+			that.size.height = that.size.height +
+				( that._helper ?
+					( that.position.top - co.top ) :
+					that.position.top );
+
+			if ( pRatio ) {
+				that.size.width = that.size.height * that.aspectRatio;
+				continueResize = false;
+			}
+			that.position.top = that._helper ? co.top : 0;
+		}
+
+		isParent = that.containerElement.get( 0 ) === that.element.parent().get( 0 );
+		isOffsetRelative = /relative|absolute/.test( that.containerElement.css( "position" ) );
+
+		if ( isParent && isOffsetRelative ) {
+			that.offset.left = that.parentData.left + that.position.left;
+			that.offset.top = that.parentData.top + that.position.top;
+		} else {
+			that.offset.left = that.element.offset().left;
+			that.offset.top = that.element.offset().top;
+		}
+
+		woset = Math.abs( that.sizeDiff.width +
+			(that._helper ?
+				that.offset.left - cop.left :
+				(that.offset.left - co.left)) );
+
+		hoset = Math.abs( that.sizeDiff.height +
+			(that._helper ?
+				that.offset.top - cop.top :
+				(that.offset.top - co.top)) );
+
+		if ( woset + that.size.width >= that.parentData.width ) {
+			that.size.width = that.parentData.width - woset;
+			if ( pRatio ) {
+				that.size.height = that.size.width / that.aspectRatio;
+				continueResize = false;
+			}
+		}
+
+		if ( hoset + that.size.height >= that.parentData.height ) {
+			that.size.height = that.parentData.height - hoset;
+			if ( pRatio ) {
+				that.size.width = that.size.height * that.aspectRatio;
+				continueResize = false;
+			}
+		}
+
+		if ( !continueResize ) {
+			that.position.left = that.prevPosition.left;
+			that.position.top = that.prevPosition.top;
+			that.size.width = that.prevSize.width;
+			that.size.height = that.prevSize.height;
+		}
+	},
+
+	stop: function() {
+		var that = $( this ).resizable( "instance" ),
+			o = that.options,
+			co = that.containerOffset,
+			cop = that.containerPosition,
+			ce = that.containerElement,
+			helper = $( that.helper ),
+			ho = helper.offset(),
+			w = helper.outerWidth() - that.sizeDiff.width,
+			h = helper.outerHeight() - that.sizeDiff.height;
+
+		if ( that._helper && !o.animate && ( /relative/ ).test( ce.css( "position" ) ) ) {
+			$( this ).css({
+				left: ho.left - cop.left - co.left,
+				width: w,
+				height: h
+			});
+		}
+
+		if ( that._helper && !o.animate && ( /static/ ).test( ce.css( "position" ) ) ) {
+			$( this ).css({
+				left: ho.left - cop.left - co.left,
+				width: w,
+				height: h
+			});
+		}
+	}
+});
+
+$.ui.plugin.add("resizable", "alsoResize", {
+
+	start: function() {
+		var that = $(this).resizable( "instance" ),
+			o = that.options;
+
+		$(o.alsoResize).each(function() {
+			var el = $(this);
+			el.data("ui-resizable-alsoresize", {
+				width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
+				left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10)
+			});
+		});
+	},
+
+	resize: function(event, ui) {
+		var that = $(this).resizable( "instance" ),
+			o = that.options,
+			os = that.originalSize,
+			op = that.originalPosition,
+			delta = {
+				height: (that.size.height - os.height) || 0,
+				width: (that.size.width - os.width) || 0,
+				top: (that.position.top - op.top) || 0,
+				left: (that.position.left - op.left) || 0
+			};
+
+			$(o.alsoResize).each(function() {
+				var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {},
+					css = el.parents(ui.originalElement[0]).length ?
+							[ "width", "height" ] :
+							[ "width", "height", "top", "left" ];
+
+				$.each(css, function(i, prop) {
+					var sum = (start[prop] || 0) + (delta[prop] || 0);
+					if (sum && sum >= 0) {
+						style[prop] = sum || null;
+					}
+				});
+
+				el.css(style);
+			});
+	},
+
+	stop: function() {
+		$(this).removeData("resizable-alsoresize");
+	}
+});
+
+$.ui.plugin.add("resizable", "ghost", {
+
+	start: function() {
+
+		var that = $(this).resizable( "instance" ), o = that.options, cs = that.size;
+
+		that.ghost = that.originalElement.clone();
+		that.ghost
+			.css({
+				opacity: 0.25,
+				display: "block",
+				position: "relative",
+				height: cs.height,
+				width: cs.width,
+				margin: 0,
+				left: 0,
+				top: 0
+			})
+			.addClass("ui-resizable-ghost")
+			.addClass(typeof o.ghost === "string" ? o.ghost : "");
+
+		that.ghost.appendTo(that.helper);
+
+	},
+
+	resize: function() {
+		var that = $(this).resizable( "instance" );
+		if (that.ghost) {
+			that.ghost.css({
+				position: "relative",
+				height: that.size.height,
+				width: that.size.width
+			});
+		}
+	},
+
+	stop: function() {
+		var that = $(this).resizable( "instance" );
+		if (that.ghost && that.helper) {
+			that.helper.get(0).removeChild(that.ghost.get(0));
+		}
+	}
+
+});
+
+$.ui.plugin.add("resizable", "grid", {
+
+	resize: function() {
+		var outerDimensions,
+			that = $(this).resizable( "instance" ),
+			o = that.options,
+			cs = that.size,
+			os = that.originalSize,
+			op = that.originalPosition,
+			a = that.axis,
+			grid = typeof o.grid === "number" ? [ o.grid, o.grid ] : o.grid,
+			gridX = (grid[0] || 1),
+			gridY = (grid[1] || 1),
+			ox = Math.round((cs.width - os.width) / gridX) * gridX,
+			oy = Math.round((cs.height - os.height) / gridY) * gridY,
+			newWidth = os.width + ox,
+			newHeight = os.height + oy,
+			isMaxWidth = o.maxWidth && (o.maxWidth < newWidth),
+			isMaxHeight = o.maxHeight && (o.maxHeight < newHeight),
+			isMinWidth = o.minWidth && (o.minWidth > newWidth),
+			isMinHeight = o.minHeight && (o.minHeight > newHeight);
+
+		o.grid = grid;
+
+		if (isMinWidth) {
+			newWidth += gridX;
+		}
+		if (isMinHeight) {
+			newHeight += gridY;
+		}
+		if (isMaxWidth) {
+			newWidth -= gridX;
+		}
+		if (isMaxHeight) {
+			newHeight -= gridY;
+		}
+
+		if (/^(se|s|e)$/.test(a)) {
+			that.size.width = newWidth;
+			that.size.height = newHeight;
+		} else if (/^(ne)$/.test(a)) {
+			that.size.width = newWidth;
+			that.size.height = newHeight;
+			that.position.top = op.top - oy;
+		} else if (/^(sw)$/.test(a)) {
+			that.size.width = newWidth;
+			that.size.height = newHeight;
+			that.position.left = op.left - ox;
+		} else {
+			if ( newHeight - gridY <= 0 || newWidth - gridX <= 0) {
+				outerDimensions = that._getPaddingPlusBorderDimensions( this );
+			}
+
+			if ( newHeight - gridY > 0 ) {
+				that.size.height = newHeight;
+				that.position.top = op.top - oy;
+			} else {
+				newHeight = gridY - outerDimensions.height;
+				that.size.height = newHeight;
+				that.position.top = op.top + os.height - newHeight;
+			}
+			if ( newWidth - gridX > 0 ) {
+				that.size.width = newWidth;
+				that.position.left = op.left - ox;
+			} else {
+				newWidth = gridX - outerDimensions.width;
+				that.size.width = newWidth;
+				that.position.left = op.left + os.width - newWidth;
+			}
+		}
+	}
+
+});
+
+var resizable = $.ui.resizable;
+
+
+/*!
+ * jQuery UI Selectable 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/selectable/
+ */
+
+
+var selectable = $.widget("ui.selectable", $.ui.mouse, {
+	version: "1.11.4",
+	options: {
+		appendTo: "body",
+		autoRefresh: true,
+		distance: 0,
+		filter: "*",
+		tolerance: "touch",
+
+		// callbacks
+		selected: null,
+		selecting: null,
+		start: null,
+		stop: null,
+		unselected: null,
+		unselecting: null
+	},
+	_create: function() {
+		var selectees,
+			that = this;
+
+		this.element.addClass("ui-selectable");
+
+		this.dragged = false;
+
+		// cache selectee children based on filter
+		this.refresh = function() {
+			selectees = $(that.options.filter, that.element[0]);
+			selectees.addClass("ui-selectee");
+			selectees.each(function() {
+				var $this = $(this),
+					pos = $this.offset();
+				$.data(this, "selectable-item", {
+					element: this,
+					$element: $this,
+					left: pos.left,
+					top: pos.top,
+					right: pos.left + $this.outerWidth(),
+					bottom: pos.top + $this.outerHeight(),
+					startselected: false,
+					selected: $this.hasClass("ui-selected"),
+					selecting: $this.hasClass("ui-selecting"),
+					unselecting: $this.hasClass("ui-unselecting")
+				});
+			});
+		};
+		this.refresh();
+
+		this.selectees = selectees.addClass("ui-selectee");
+
+		this._mouseInit();
+
+		this.helper = $("<div class='ui-selectable-helper'></div>");
+	},
+
+	_destroy: function() {
+		this.selectees
+			.removeClass("ui-selectee")
+			.removeData("selectable-item");
+		this.element
+			.removeClass("ui-selectable ui-selectable-disabled");
+		this._mouseDestroy();
+	},
+
+	_mouseStart: function(event) {
+		var that = this,
+			options = this.options;
+
+		this.opos = [ event.pageX, event.pageY ];
+
+		if (this.options.disabled) {
+			return;
+		}
+
+		this.selectees = $(options.filter, this.element[0]);
+
+		this._trigger("start", event);
+
+		$(options.appendTo).append(this.helper);
+		// position helper (lasso)
+		this.helper.css({
+			"left": event.pageX,
+			"top": event.pageY,
+			"width": 0,
+			"height": 0
+		});
+
+		if (options.autoRefresh) {
+			this.refresh();
+		}
+
+		this.selectees.filter(".ui-selected").each(function() {
+			var selectee = $.data(this, "selectable-item");
+			selectee.startselected = true;
+			if (!event.metaKey && !event.ctrlKey) {
+				selectee.$element.removeClass("ui-selected");
+				selectee.selected = false;
+				selectee.$element.addClass("ui-unselecting");
+				selectee.unselecting = true;
+				// selectable UNSELECTING callback
+				that._trigger("unselecting", event, {
+					unselecting: selectee.element
+				});
+			}
+		});
+
+		$(event.target).parents().addBack().each(function() {
+			var doSelect,
+				selectee = $.data(this, "selectable-item");
+			if (selectee) {
+				doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass("ui-selected");
+				selectee.$element
+					.removeClass(doSelect ? "ui-unselecting" : "ui-selected")
+					.addClass(doSelect ? "ui-selecting" : "ui-unselecting");
+				selectee.unselecting = !doSelect;
+				selectee.selecting = doSelect;
+				selectee.selected = doSelect;
+				// selectable (UN)SELECTING callback
+				if (doSelect) {
+					that._trigger("selecting", event, {
+						selecting: selectee.element
+					});
+				} else {
+					that._trigger("unselecting", event, {
+						unselecting: selectee.element
+					});
+				}
+				return false;
+			}
+		});
+
+	},
+
+	_mouseDrag: function(event) {
+
+		this.dragged = true;
+
+		if (this.options.disabled) {
+			return;
+		}
+
+		var tmp,
+			that = this,
+			options = this.options,
+			x1 = this.opos[0],
+			y1 = this.opos[1],
+			x2 = event.pageX,
+			y2 = event.pageY;
+
+		if (x1 > x2) { tmp = x2; x2 = x1; x1 = tmp; }
+		if (y1 > y2) { tmp = y2; y2 = y1; y1 = tmp; }
+		this.helper.css({ left: x1, top: y1, width: x2 - x1, height: y2 - y1 });
+
+		this.selectees.each(function() {
+			var selectee = $.data(this, "selectable-item"),
+				hit = false;
+
+			//prevent helper from being selected if appendTo: selectable
+			if (!selectee || selectee.element === that.element[0]) {
+				return;
+			}
+
+			if (options.tolerance === "touch") {
+				hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
+			} else if (options.tolerance === "fit") {
+				hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
+			}
+
+			if (hit) {
+				// SELECT
+				if (selectee.selected) {
+					selectee.$element.removeClass("ui-selected");
+					selectee.selected = false;
+				}
+				if (selectee.unselecting) {
+					selectee.$element.removeClass("ui-unselecting");
+					selectee.unselecting = false;
+				}
+				if (!selectee.selecting) {
+					selectee.$element.addClass("ui-selecting");
+					selectee.selecting = true;
+					// selectable SELECTING callback
+					that._trigger("selecting", event, {
+						selecting: selectee.element
+					});
+				}
+			} else {
+				// UNSELECT
+				if (selectee.selecting) {
+					if ((event.metaKey || event.ctrlKey) && selectee.startselected) {
+						selectee.$element.removeClass("ui-selecting");
+						selectee.selecting = false;
+						selectee.$element.addClass("ui-selected");
+						selectee.selected = true;
+					} else {
+						selectee.$element.removeClass("ui-selecting");
+						selectee.selecting = false;
+						if (selectee.startselected) {
+							selectee.$element.addClass("ui-unselecting");
+							selectee.unselecting = true;
+						}
+						// selectable UNSELECTING callback
+						that._trigger("unselecting", event, {
+							unselecting: selectee.element
+						});
+					}
+				}
+				if (selectee.selected) {
+					if (!event.metaKey && !event.ctrlKey && !selectee.startselected) {
+						selectee.$element.removeClass("ui-selected");
+						selectee.selected = false;
+
+						selectee.$element.addClass("ui-unselecting");
+						selectee.unselecting = true;
+						// selectable UNSELECTING callback
+						that._trigger("unselecting", event, {
+							unselecting: selectee.element
+						});
+					}
+				}
+			}
+		});
+
+		return false;
+	},
+
+	_mouseStop: function(event) {
+		var that = this;
+
+		this.dragged = false;
+
+		$(".ui-unselecting", this.element[0]).each(function() {
+			var selectee = $.data(this, "selectable-item");
+			selectee.$element.removeClass("ui-unselecting");
+			selectee.unselecting = false;
+			selectee.startselected = false;
+			that._trigger("unselected", event, {
+				unselected: selectee.element
+			});
+		});
+		$(".ui-selecting", this.element[0]).each(function() {
+			var selectee = $.data(this, "selectable-item");
+			selectee.$element.removeClass("ui-selecting").addClass("ui-selected");
+			selectee.selecting = false;
+			selectee.selected = true;
+			selectee.startselected = true;
+			that._trigger("selected", event, {
+				selected: selectee.element
+			});
+		});
+		this._trigger("stop", event);
+
+		this.helper.remove();
+
+		return false;
+	}
+
+});
+
+
+/*!
+ * jQuery UI Sortable 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/sortable/
+ */
+
+
+var sortable = $.widget("ui.sortable", $.ui.mouse, {
+	version: "1.11.4",
+	widgetEventPrefix: "sort",
+	ready: false,
+	options: {
+		appendTo: "parent",
+		axis: false,
+		connectWith: false,
+		containment: false,
+		cursor: "auto",
+		cursorAt: false,
+		dropOnEmpty: true,
+		forcePlaceholderSize: false,
+		forceHelperSize: false,
+		grid: false,
+		handle: false,
+		helper: "original",
+		items: "> *",
+		opacity: false,
+		placeholder: false,
+		revert: false,
+		scroll: true,
+		scrollSensitivity: 20,
+		scrollSpeed: 20,
+		scope: "default",
+		tolerance: "intersect",
+		zIndex: 1000,
+
+		// callbacks
+		activate: null,
+		beforeStop: null,
+		change: null,
+		deactivate: null,
+		out: null,
+		over: null,
+		receive: null,
+		remove: null,
+		sort: null,
+		start: null,
+		stop: null,
+		update: null
+	},
+
+	_isOverAxis: function( x, reference, size ) {
+		return ( x >= reference ) && ( x < ( reference + size ) );
+	},
+
+	_isFloating: function( item ) {
+		return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display"));
+	},
+
+	_create: function() {
+		this.containerCache = {};
+		this.element.addClass("ui-sortable");
+
+		//Get the items
+		this.refresh();
+
+		//Let's determine the parent's offset
+		this.offset = this.element.offset();
+
+		//Initialize mouse events for interaction
+		this._mouseInit();
+
+		this._setHandleClassName();
+
+		//We're ready to go
+		this.ready = true;
+
+	},
+
+	_setOption: function( key, value ) {
+		this._super( key, value );
+
+		if ( key === "handle" ) {
+			this._setHandleClassName();
+		}
+	},
+
+	_setHandleClassName: function() {
+		this.element.find( ".ui-sortable-handle" ).removeClass( "ui-sortable-handle" );
+		$.each( this.items, function() {
+			( this.instance.options.handle ?
+				this.item.find( this.instance.options.handle ) : this.item )
+				.addClass( "ui-sortable-handle" );
+		});
+	},
+
+	_destroy: function() {
+		this.element
+			.removeClass( "ui-sortable ui-sortable-disabled" )
+			.find( ".ui-sortable-handle" )
+				.removeClass( "ui-sortable-handle" );
+		this._mouseDestroy();
+
+		for ( var i = this.items.length - 1; i >= 0; i-- ) {
+			this.items[i].item.removeData(this.widgetName + "-item");
+		}
+
+		return this;
+	},
+
+	_mouseCapture: function(event, overrideHandle) {
+		var currentItem = null,
+			validHandle = false,
+			that = this;
+
+		if (this.reverting) {
+			return false;
+		}
+
+		if(this.options.disabled || this.options.type === "static") {
+			return false;
+		}
+
+		//We have to refresh the items data once first
+		this._refreshItems(event);
+
+		//Find out if the clicked node (or one of its parents) is a actual item in this.items
+		$(event.target).parents().each(function() {
+			if($.data(this, that.widgetName + "-item") === that) {
+				currentItem = $(this);
+				return false;
+			}
+		});
+		if($.data(event.target, that.widgetName + "-item") === that) {
+			currentItem = $(event.target);
+		}
+
+		if(!currentItem) {
+			return false;
+		}
+		if(this.options.handle && !overrideHandle) {
+			$(this.options.handle, currentItem).find("*").addBack().each(function() {
+				if(this === event.target) {
+					validHandle = true;
+				}
+			});
+			if(!validHandle) {
+				return false;
+			}
+		}
+
+		this.currentItem = currentItem;
+		this._removeCurrentsFromItems();
+		return true;
+
+	},
+
+	_mouseStart: function(event, overrideHandle, noActivation) {
+
+		var i, body,
+			o = this.options;
+
+		this.currentContainer = this;
+
+		//We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
+		this.refreshPositions();
+
+		//Create and append the visible helper
+		this.helper = this._createHelper(event);
+
+		//Cache the helper size
+		this._cacheHelperProportions();
+
+		/*
+		 * - Position generation -
+		 * This block generates everything position related - it's the core of draggables.
+		 */
+
+		//Cache the margins of the original element
+		this._cacheMargins();
+
+		//Get the next scrolling parent
+		this.scrollParent = this.helper.scrollParent();
+
+		//The element's absolute position on the page minus margins
+		this.offset = this.currentItem.offset();
+		this.offset = {
+			top: this.offset.top - this.margins.top,
+			left: this.offset.left - this.margins.left
+		};
+
+		$.extend(this.offset, {
+			click: { //Where the click happened, relative to the element
+				left: event.pageX - this.offset.left,
+				top: event.pageY - this.offset.top
+			},
+			parent: this._getParentOffset(),
+			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
+		});
+
+		// Only after we got the offset, we can change the helper's position to absolute
+		// TODO: Still need to figure out a way to make relative sorting possible
+		this.helper.css("position", "absolute");
+		this.cssPosition = this.helper.css("position");
+
+		//Generate the original position
+		this.originalPosition = this._generatePosition(event);
+		this.originalPageX = event.pageX;
+		this.originalPageY = event.pageY;
+
+		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
+		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
+
+		//Cache the former DOM position
+		this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
+
+		//If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
+		if(this.helper[0] !== this.currentItem[0]) {
+			this.currentItem.hide();
+		}
+
+		//Create the placeholder
+		this._createPlaceholder();
+
+		//Set a containment if given in the options
+		if(o.containment) {
+			this._setContainment();
+		}
+
+		if( o.cursor && o.cursor !== "auto" ) { // cursor option
+			body = this.document.find( "body" );
+
+			// support: IE
+			this.storedCursor = body.css( "cursor" );
+			body.css( "cursor", o.cursor );
+
+			this.storedStylesheet = $( "<style>*{ cursor: "+o.cursor+" !important; }</style>" ).appendTo( body );
+		}
+
+		if(o.opacity) { // opacity option
+			if (this.helper.css("opacity")) {
+				this._storedOpacity = this.helper.css("opacity");
+			}
+			this.helper.css("opacity", o.opacity);
+		}
+
+		if(o.zIndex) { // zIndex option
+			if (this.helper.css("zIndex")) {
+				this._storedZIndex = this.helper.css("zIndex");
+			}
+			this.helper.css("zIndex", o.zIndex);
+		}
+
+		//Prepare scrolling
+		if(this.scrollParent[0] !== this.document[0] && this.scrollParent[0].tagName !== "HTML") {
+			this.overflowOffset = this.scrollParent.offset();
+		}
+
+		//Call callbacks
+		this._trigger("start", event, this._uiHash());
+
+		//Recache the helper size
+		if(!this._preserveHelperProportions) {
+			this._cacheHelperProportions();
+		}
+
+
+		//Post "activate" events to possible containers
+		if( !noActivation ) {
+			for ( i = this.containers.length - 1; i >= 0; i-- ) {
+				this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
+			}
+		}
+
+		//Prepare possible droppables
+		if($.ui.ddmanager) {
+			$.ui.ddmanager.current = this;
+		}
+
+		if ($.ui.ddmanager && !o.dropBehaviour) {
+			$.ui.ddmanager.prepareOffsets(this, event);
+		}
+
+		this.dragging = true;
+
+		this.helper.addClass("ui-sortable-helper");
+		this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
+		return true;
+
+	},
+
+	_mouseDrag: function(event) {
+		var i, item, itemElement, intersection,
+			o = this.options,
+			scrolled = false;
+
+		//Compute the helpers position
+		this.position = this._generatePosition(event);
+		this.positionAbs = this._convertPositionTo("absolute");
+
+		if (!this.lastPositionAbs) {
+			this.lastPositionAbs = this.positionAbs;
+		}
+
+		//Do scrolling
+		if(this.options.scroll) {
+			if(this.scrollParent[0] !== this.document[0] && this.scrollParent[0].tagName !== "HTML") {
+
+				if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
+					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
+				} else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) {
+					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
+				}
+
+				if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
+					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
+				} else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) {
+					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
+				}
+
+			} else {
+
+				if(event.pageY - this.document.scrollTop() < o.scrollSensitivity) {
+					scrolled = this.document.scrollTop(this.document.scrollTop() - o.scrollSpeed);
+				} else if(this.window.height() - (event.pageY - this.document.scrollTop()) < o.scrollSensitivity) {
+					scrolled = this.document.scrollTop(this.document.scrollTop() + o.scrollSpeed);
+				}
+
+				if(event.pageX - this.document.scrollLeft() < o.scrollSensitivity) {
+					scrolled = this.document.scrollLeft(this.document.scrollLeft() - o.scrollSpeed);
+				} else if(this.window.width() - (event.pageX - this.document.scrollLeft()) < o.scrollSensitivity) {
+					scrolled = this.document.scrollLeft(this.document.scrollLeft() + o.scrollSpeed);
+				}
+
+			}
+
+			if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
+				$.ui.ddmanager.prepareOffsets(this, event);
+			}
+		}
+
+		//Regenerate the absolute position used for position checks
+		this.positionAbs = this._convertPositionTo("absolute");
+
+		//Set the helper position
+		if(!this.options.axis || this.options.axis !== "y") {
+			this.helper[0].style.left = this.position.left+"px";
+		}
+		if(!this.options.axis || this.options.axis !== "x") {
+			this.helper[0].style.top = this.position.top+"px";
+		}
+
+		//Rearrange
+		for (i = this.items.length - 1; i >= 0; i--) {
+
+			//Cache variables and intersection, continue if no intersection
+			item = this.items[i];
+			itemElement = item.item[0];
+			intersection = this._intersectsWithPointer(item);
+			if (!intersection) {
+				continue;
+			}
+
+			// Only put the placeholder inside the current Container, skip all
+			// items from other containers. This works because when moving
+			// an item from one container to another the
+			// currentContainer is switched before the placeholder is moved.
+			//
+			// Without this, moving items in "sub-sortables" can cause
+			// the placeholder to jitter between the outer and inner container.
+			if (item.instance !== this.currentContainer) {
+				continue;
+			}
+
+			// cannot intersect with itself
+			// no useless actions that have been done before
+			// no action if the item moved is the parent of the item checked
+			if (itemElement !== this.currentItem[0] &&
+				this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement &&
+				!$.contains(this.placeholder[0], itemElement) &&
+				(this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true)
+			) {
+
+				this.direction = intersection === 1 ? "down" : "up";
+
+				if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) {
+					this._rearrange(event, item);
+				} else {
+					break;
+				}
+
+				this._trigger("change", event, this._uiHash());
+				break;
+			}
+		}
+
+		//Post events to containers
+		this._contactContainers(event);
+
+		//Interconnect with droppables
+		if($.ui.ddmanager) {
+			$.ui.ddmanager.drag(this, event);
+		}
+
+		//Call callbacks
+		this._trigger("sort", event, this._uiHash());
+
+		this.lastPositionAbs = this.positionAbs;
+		return false;
+
+	},
+
+	_mouseStop: function(event, noPropagation) {
+
+		if(!event) {
+			return;
+		}
+
+		//If we are using droppables, inform the manager about the drop
+		if ($.ui.ddmanager && !this.options.dropBehaviour) {
+			$.ui.ddmanager.drop(this, event);
+		}
+
+		if(this.options.revert) {
+			var that = this,
+				cur = this.placeholder.offset(),
+				axis = this.options.axis,
+				animation = {};
+
+			if ( !axis || axis === "x" ) {
+				animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === this.document[0].body ? 0 : this.offsetParent[0].scrollLeft);
+			}
+			if ( !axis || axis === "y" ) {
+				animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === this.document[0].body ? 0 : this.offsetParent[0].scrollTop);
+			}
+			this.reverting = true;
+			$(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() {
+				that._clear(event);
+			});
+		} else {
+			this._clear(event, noPropagation);
+		}
+
+		return false;
+
+	},
+
+	cancel: function() {
+
+		if(this.dragging) {
+
+			this._mouseUp({ target: null });
+
+			if(this.options.helper === "original") {
+				this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
+			} else {
+				this.currentItem.show();
+			}
+
+			//Post deactivating events to containers
+			for (var i = this.containers.length - 1; i >= 0; i--){
+				this.containers[i]._trigger("deactivate", null, this._uiHash(this));
+				if(this.containers[i].containerCache.over) {
+					this.containers[i]._trigger("out", null, this._uiHash(this));
+					this.containers[i].containerCache.over = 0;
+				}
+			}
+
+		}
+
+		if (this.placeholder) {
+			//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
+			if(this.placeholder[0].parentNode) {
+				this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
+			}
+			if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) {
+				this.helper.remove();
+			}
+
+			$.extend(this, {
+				helper: null,
+				dragging: false,
+				reverting: false,
+				_noFinalSort: null
+			});
+
+			if(this.domPosition.prev) {
+				$(this.domPosition.prev).after(this.currentItem);
+			} else {
+				$(this.domPosition.parent).prepend(this.currentItem);
+			}
+		}
+
+		return this;
+
+	},
+
+	serialize: function(o) {
+
+		var items = this._getItemsAsjQuery(o && o.connected),
+			str = [];
+		o = o || {};
+
+		$(items).each(function() {
+			var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/));
+			if (res) {
+				str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2]));
+			}
+		});
+
+		if(!str.length && o.key) {
+			str.push(o.key + "=");
+		}
+
+		return str.join("&");
+
+	},
+
+	toArray: function(o) {
+
+		var items = this._getItemsAsjQuery(o && o.connected),
+			ret = [];
+
+		o = o || {};
+
+		items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); });
+		return ret;
+
+	},
+
+	/* Be careful with the following core functions */
+	_intersectsWith: function(item) {
+
+		var x1 = this.positionAbs.left,
+			x2 = x1 + this.helperProportions.width,
+			y1 = this.positionAbs.top,
+			y2 = y1 + this.helperProportions.height,
+			l = item.left,
+			r = l + item.width,
+			t = item.top,
+			b = t + item.height,
+			dyClick = this.offset.click.top,
+			dxClick = this.offset.click.left,
+			isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ),
+			isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ),
+			isOverElement = isOverElementHeight && isOverElementWidth;
+
+		if ( this.options.tolerance === "pointer" ||
+			this.options.forcePointerForContainers ||
+			(this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"])
+		) {
+			return isOverElement;
+		} else {
+
+			return (l < x1 + (this.helperProportions.width / 2) && // Right Half
+				x2 - (this.helperProportions.width / 2) < r && // Left Half
+				t < y1 + (this.helperProportions.height / 2) && // Bottom Half
+				y2 - (this.helperProportions.height / 2) < b ); // Top Half
+
+		}
+	},
+
+	_intersectsWithPointer: function(item) {
+
+		var isOverElementHeight = (this.options.axis === "x") || this._isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
+			isOverElementWidth = (this.options.axis === "y") || this._isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
+			isOverElement = isOverElementHeight && isOverElementWidth,
+			verticalDirection = this._getDragVerticalDirection(),
+			horizontalDirection = this._getDragHorizontalDirection();
+
+		if (!isOverElement) {
+			return false;
+		}
+
+		return this.floating ?
+			( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 )
+			: ( verticalDirection && (verticalDirection === "down" ? 2 : 1) );
+
+	},
+
+	_intersectsWithSides: function(item) {
+
+		var isOverBottomHalf = this._isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
+			isOverRightHalf = this._isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
+			verticalDirection = this._getDragVerticalDirection(),
+			horizontalDirection = this._getDragHorizontalDirection();
+
+		if (this.floating && horizontalDirection) {
+			return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf));
+		} else {
+			return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf));
+		}
+
+	},
+
+	_getDragVerticalDirection: function() {
+		var delta = this.positionAbs.top - this.lastPositionAbs.top;
+		return delta !== 0 && (delta > 0 ? "down" : "up");
+	},
+
+	_getDragHorizontalDirection: function() {
+		var delta = this.positionAbs.left - this.lastPositionAbs.left;
+		return delta !== 0 && (delta > 0 ? "right" : "left");
+	},
+
+	refresh: function(event) {
+		this._refreshItems(event);
+		this._setHandleClassName();
+		this.refreshPositions();
+		return this;
+	},
+
+	_connectWith: function() {
+		var options = this.options;
+		return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith;
+	},
+
+	_getItemsAsjQuery: function(connected) {
+
+		var i, j, cur, inst,
+			items = [],
+			queries = [],
+			connectWith = this._connectWith();
+
+		if(connectWith && connected) {
+			for (i = connectWith.length - 1; i >= 0; i--){
+				cur = $(connectWith[i], this.document[0]);
+				for ( j = cur.length - 1; j >= 0; j--){
+					inst = $.data(cur[j], this.widgetFullName);
+					if(inst && inst !== this && !inst.options.disabled) {
+						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]);
+					}
+				}
+			}
+		}
+
+		queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]);
+
+		function addItems() {
+			items.push( this );
+		}
+		for (i = queries.length - 1; i >= 0; i--){
+			queries[i][0].each( addItems );
+		}
+
+		return $(items);
+
+	},
+
+	_removeCurrentsFromItems: function() {
+
+		var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
+
+		this.items = $.grep(this.items, function (item) {
+			for (var j=0; j < list.length; j++) {
+				if(list[j] === item.item[0]) {
+					return false;
+				}
+			}
+			return true;
+		});
+
+	},
+
+	_refreshItems: function(event) {
+
+		this.items = [];
+		this.containers = [this];
+
+		var i, j, cur, inst, targetData, _queries, item, queriesLength,
+			items = this.items,
+			queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]],
+			connectWith = this._connectWith();
+
+		if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
+			for (i = connectWith.length - 1; i >= 0; i--){
+				cur = $(connectWith[i], this.document[0]);
+				for (j = cur.length - 1; j >= 0; j--){
+					inst = $.data(cur[j], this.widgetFullName);
+					if(inst && inst !== this && !inst.options.disabled) {
+						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
+						this.containers.push(inst);
+					}
+				}
+			}
+		}
+
+		for (i = queries.length - 1; i >= 0; i--) {
+			targetData = queries[i][1];
+			_queries = queries[i][0];
+
+			for (j=0, queriesLength = _queries.length; j < queriesLength; j++) {
+				item = $(_queries[j]);
+
+				item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager)
+
+				items.push({
+					item: item,
+					instance: targetData,
+					width: 0, height: 0,
+					left: 0, top: 0
+				});
+			}
+		}
+
+	},
+
+	refreshPositions: function(fast) {
+
+		// Determine whether items are being displayed horizontally
+		this.floating = this.items.length ?
+			this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) :
+			false;
+
+		//This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
+		if(this.offsetParent && this.helper) {
+			this.offset.parent = this._getParentOffset();
+		}
+
+		var i, item, t, p;
+
+		for (i = this.items.length - 1; i >= 0; i--){
+			item = this.items[i];
+
+			//We ignore calculating positions of all connected containers when we're not over them
+			if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) {
+				continue;
+			}
+
+			t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
+
+			if (!fast) {
+				item.width = t.outerWidth();
+				item.height = t.outerHeight();
+			}
+
+			p = t.offset();
+			item.left = p.left;
+			item.top = p.top;
+		}
+
+		if(this.options.custom && this.options.custom.refreshContainers) {
+			this.options.custom.refreshContainers.call(this);
+		} else {
+			for (i = this.containers.length - 1; i >= 0; i--){
+				p = this.containers[i].element.offset();
+				this.containers[i].containerCache.left = p.left;
+				this.containers[i].containerCache.top = p.top;
+				this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
+				this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
+			}
+		}
+
+		return this;
+	},
+
+	_createPlaceholder: function(that) {
+		that = that || this;
+		var className,
+			o = that.options;
+
+		if(!o.placeholder || o.placeholder.constructor === String) {
+			className = o.placeholder;
+			o.placeholder = {
+				element: function() {
+
+					var nodeName = that.currentItem[0].nodeName.toLowerCase(),
+						element = $( "<" + nodeName + ">", that.document[0] )
+							.addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
+							.removeClass("ui-sortable-helper");
+
+					if ( nodeName === "tbody" ) {
+						that._createTrPlaceholder(
+							that.currentItem.find( "tr" ).eq( 0 ),
+							$( "<tr>", that.document[ 0 ] ).appendTo( element )
+						);
+					} else if ( nodeName === "tr" ) {
+						that._createTrPlaceholder( that.currentItem, element );
+					} else if ( nodeName === "img" ) {
+						element.attr( "src", that.currentItem.attr( "src" ) );
+					}
+
+					if ( !className ) {
+						element.css( "visibility", "hidden" );
+					}
+
+					return element;
+				},
+				update: function(container, p) {
+
+					// 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
+					// 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
+					if(className && !o.forcePlaceholderSize) {
+						return;
+					}
+
+					//If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
+					if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); }
+					if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); }
+				}
+			};
+		}
+
+		//Create the placeholder
+		that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));
+
+		//Append it after the actual current item
+		that.currentItem.after(that.placeholder);
+
+		//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
+		o.placeholder.update(that, that.placeholder);
+
+	},
+
+	_createTrPlaceholder: function( sourceTr, targetTr ) {
+		var that = this;
+
+		sourceTr.children().each(function() {
+			$( "<td>&#160;</td>", that.document[ 0 ] )
+				.attr( "colspan", $( this ).attr( "colspan" ) || 1 )
+				.appendTo( targetTr );
+		});
+	},
+
+	_contactContainers: function(event) {
+		var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom, floating, axis,
+			innermostContainer = null,
+			innermostIndex = null;
+
+		// get innermost container that intersects with item
+		for (i = this.containers.length - 1; i >= 0; i--) {
+
+			// never consider a container that's located within the item itself
+			if($.contains(this.currentItem[0], this.containers[i].element[0])) {
+				continue;
+			}
+
+			if(this._intersectsWith(this.containers[i].containerCache)) {
+
+				// if we've already found a container and it's more "inner" than this, then continue
+				if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) {
+					continue;
+				}
+
+				innermostContainer = this.containers[i];
+				innermostIndex = i;
+
+			} else {
+				// container doesn't intersect. trigger "out" event if necessary
+				if(this.containers[i].containerCache.over) {
+					this.containers[i]._trigger("out", event, this._uiHash(this));
+					this.containers[i].containerCache.over = 0;
+				}
+			}
+
+		}
+
+		// if no intersecting containers found, return
+		if(!innermostContainer) {
+			return;
+		}
+
+		// move the item into the container if it's not there already
+		if(this.containers.length === 1) {
+			if (!this.containers[innermostIndex].containerCache.over) {
+				this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
+				this.containers[innermostIndex].containerCache.over = 1;
+			}
+		} else {
+
+			//When entering a new container, we will find the item with the least distance and append our item near it
+			dist = 10000;
+			itemWithLeastDistance = null;
+			floating = innermostContainer.floating || this._isFloating(this.currentItem);
+			posProperty = floating ? "left" : "top";
+			sizeProperty = floating ? "width" : "height";
+			axis = floating ? "clientX" : "clientY";
+
+			for (j = this.items.length - 1; j >= 0; j--) {
+				if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) {
+					continue;
+				}
+				if(this.items[j].item[0] === this.currentItem[0]) {
+					continue;
+				}
+
+				cur = this.items[j].item.offset()[posProperty];
+				nearBottom = false;
+				if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) {
+					nearBottom = true;
+				}
+
+				if ( Math.abs( event[ axis ] - cur ) < dist ) {
+					dist = Math.abs( event[ axis ] - cur );
+					itemWithLeastDistance = this.items[ j ];
+					this.direction = nearBottom ? "up": "down";
+				}
+			}
+
+			//Check if dropOnEmpty is enabled
+			if(!itemWithLeastDistance && !this.options.dropOnEmpty) {
+				return;
+			}
+
+			if(this.currentContainer === this.containers[innermostIndex]) {
+				if ( !this.currentContainer.containerCache.over ) {
+					this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash() );
+					this.currentContainer.containerCache.over = 1;
+				}
+				return;
+			}
+
+			itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
+			this._trigger("change", event, this._uiHash());
+			this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
+			this.currentContainer = this.containers[innermostIndex];
+
+			//Update the placeholder
+			this.options.placeholder.update(this.currentContainer, this.placeholder);
+
+			this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
+			this.containers[innermostIndex].containerCache.over = 1;
+		}
+
+
+	},
+
+	_createHelper: function(event) {
+
+		var o = this.options,
+			helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem);
+
+		//Add the helper to the DOM if that didn't happen already
+		if(!helper.parents("body").length) {
+			$(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
+		}
+
+		if(helper[0] === this.currentItem[0]) {
+			this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
+		}
+
+		if(!helper[0].style.width || o.forceHelperSize) {
+			helper.width(this.currentItem.width());
+		}
+		if(!helper[0].style.height || o.forceHelperSize) {
+			helper.height(this.currentItem.height());
+		}
+
+		return helper;
+
+	},
+
+	_adjustOffsetFromHelper: function(obj) {
+		if (typeof obj === "string") {
+			obj = obj.split(" ");
+		}
+		if ($.isArray(obj)) {
+			obj = {left: +obj[0], top: +obj[1] || 0};
+		}
+		if ("left" in obj) {
+			this.offset.click.left = obj.left + this.margins.left;
+		}
+		if ("right" in obj) {
+			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+		}
+		if ("top" in obj) {
+			this.offset.click.top = obj.top + this.margins.top;
+		}
+		if ("bottom" in obj) {
+			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+		}
+	},
+
+	_getParentOffset: function() {
+
+
+		//Get the offsetParent and cache its position
+		this.offsetParent = this.helper.offsetParent();
+		var po = this.offsetParent.offset();
+
+		// This is a special case where we need to modify a offset calculated on start, since the following happened:
+		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
+		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
+		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
+		if(this.cssPosition === "absolute" && this.scrollParent[0] !== this.document[0] && $.contains(this.scrollParent[0], this.offsetParent[0])) {
+			po.left += this.scrollParent.scrollLeft();
+			po.top += this.scrollParent.scrollTop();
+		}
+
+		// This needs to be actually done for all browsers, since pageX/pageY includes this information
+		// with an ugly IE fix
+		if( this.offsetParent[0] === this.document[0].body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
+			po = { top: 0, left: 0 };
+		}
+
+		return {
+			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
+			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
+		};
+
+	},
+
+	_getRelativeOffset: function() {
+
+		if(this.cssPosition === "relative") {
+			var p = this.currentItem.position();
+			return {
+				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
+				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
+			};
+		} else {
+			return { top: 0, left: 0 };
+		}
+
+	},
+
+	_cacheMargins: function() {
+		this.margins = {
+			left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
+			top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
+		};
+	},
+
+	_cacheHelperProportions: function() {
+		this.helperProportions = {
+			width: this.helper.outerWidth(),
+			height: this.helper.outerHeight()
+		};
+	},
+
+	_setContainment: function() {
+
+		var ce, co, over,
+			o = this.options;
+		if(o.containment === "parent") {
+			o.containment = this.helper[0].parentNode;
+		}
+		if(o.containment === "document" || o.containment === "window") {
+			this.containment = [
+				0 - this.offset.relative.left - this.offset.parent.left,
+				0 - this.offset.relative.top - this.offset.parent.top,
+				o.containment === "document" ? this.document.width() : this.window.width() - this.helperProportions.width - this.margins.left,
+				(o.containment === "document" ? this.document.width() : this.window.height() || this.document[0].body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
+			];
+		}
+
+		if(!(/^(document|window|parent)$/).test(o.containment)) {
+			ce = $(o.containment)[0];
+			co = $(o.containment).offset();
+			over = ($(ce).css("overflow") !== "hidden");
+
+			this.containment = [
+				co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
+				co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
+				co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
+				co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
+			];
+		}
+
+	},
+
+	_convertPositionTo: function(d, pos) {
+
+		if(!pos) {
+			pos = this.position;
+		}
+		var mod = d === "absolute" ? 1 : -1,
+			scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== this.document[0] && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent,
+			scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+		return {
+			top: (
+				pos.top	+																// The absolute mouse position
+				this.offset.relative.top * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.top * mod -											// The offsetParent's offset without borders (offset + border)
+				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
+			),
+			left: (
+				pos.left +																// The absolute mouse position
+				this.offset.relative.left * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.left * mod	-										// The offsetParent's offset without borders (offset + border)
+				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
+			)
+		};
+
+	},
+
+	_generatePosition: function(event) {
+
+		var top, left,
+			o = this.options,
+			pageX = event.pageX,
+			pageY = event.pageY,
+			scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== this.document[0] && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+		// This is another very weird special case that only happens for relative elements:
+		// 1. If the css position is relative
+		// 2. and the scroll parent is the document or similar to the offset parent
+		// we have to refresh the relative offset during the scroll so there are no jumps
+		if(this.cssPosition === "relative" && !(this.scrollParent[0] !== this.document[0] && this.scrollParent[0] !== this.offsetParent[0])) {
+			this.offset.relative = this._getRelativeOffset();
+		}
+
+		/*
+		 * - Position constraining -
+		 * Constrain the position to a mix of grid, containment.
+		 */
+
+		if(this.originalPosition) { //If we are not dragging yet, we won't check for options
+
+			if(this.containment) {
+				if(event.pageX - this.offset.click.left < this.containment[0]) {
+					pageX = this.containment[0] + this.offset.click.left;
+				}
+				if(event.pageY - this.offset.click.top < this.containment[1]) {
+					pageY = this.containment[1] + this.offset.click.top;
+				}
+				if(event.pageX - this.offset.click.left > this.containment[2]) {
+					pageX = this.containment[2] + this.offset.click.left;
+				}
+				if(event.pageY - this.offset.click.top > this.containment[3]) {
+					pageY = this.containment[3] + this.offset.click.top;
+				}
+			}
+
+			if(o.grid) {
+				top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
+				pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
+
+				left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
+				pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
+			}
+
+		}
+
+		return {
+			top: (
+				pageY -																// The absolute mouse position
+				this.offset.click.top -													// Click offset (relative to the element)
+				this.offset.relative.top	-											// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.top +												// The offsetParent's offset without borders (offset + border)
+				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
+			),
+			left: (
+				pageX -																// The absolute mouse position
+				this.offset.click.left -												// Click offset (relative to the element)
+				this.offset.relative.left	-											// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.parent.left +												// The offsetParent's offset without borders (offset + border)
+				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
+			)
+		};
+
+	},
+
+	_rearrange: function(event, i, a, hardRefresh) {
+
+		a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling));
+
+		//Various things done here to improve the performance:
+		// 1. we create a setTimeout, that calls refreshPositions
+		// 2. on the instance, we have a counter variable, that get's higher after every append
+		// 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
+		// 4. this lets only the last addition to the timeout stack through
+		this.counter = this.counter ? ++this.counter : 1;
+		var counter = this.counter;
+
+		this._delay(function() {
+			if(counter === this.counter) {
+				this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
+			}
+		});
+
+	},
+
+	_clear: function(event, noPropagation) {
+
+		this.reverting = false;
+		// We delay all events that have to be triggered to after the point where the placeholder has been removed and
+		// everything else normalized again
+		var i,
+			delayedTriggers = [];
+
+		// We first have to update the dom position of the actual currentItem
+		// Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
+		if(!this._noFinalSort && this.currentItem.parent().length) {
+			this.placeholder.before(this.currentItem);
+		}
+		this._noFinalSort = null;
+
+		if(this.helper[0] === this.currentItem[0]) {
+			for(i in this._storedCSS) {
+				if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") {
+					this._storedCSS[i] = "";
+				}
+			}
+			this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
+		} else {
+			this.currentItem.show();
+		}
+
+		if(this.fromOutside && !noPropagation) {
+			delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
+		}
+		if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) {
+			delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
+		}
+
+		// Check if the items Container has Changed and trigger appropriate
+		// events.
+		if (this !== this.currentContainer) {
+			if(!noPropagation) {
+				delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
+				delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); };  }).call(this, this.currentContainer));
+				delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this));  }; }).call(this, this.currentContainer));
+			}
+		}
+
+
+		//Post events to containers
+		function delayEvent( type, instance, container ) {
+			return function( event ) {
+				container._trigger( type, event, instance._uiHash( instance ) );
+			};
+		}
+		for (i = this.containers.length - 1; i >= 0; i--){
+			if (!noPropagation) {
+				delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) );
+			}
+			if(this.containers[i].containerCache.over) {
+				delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) );
+				this.containers[i].containerCache.over = 0;
+			}
+		}
+
+		//Do what was originally in plugins
+		if ( this.storedCursor ) {
+			this.document.find( "body" ).css( "cursor", this.storedCursor );
+			this.storedStylesheet.remove();
+		}
+		if(this._storedOpacity) {
+			this.helper.css("opacity", this._storedOpacity);
+		}
+		if(this._storedZIndex) {
+			this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex);
+		}
+
+		this.dragging = false;
+
+		if(!noPropagation) {
+			this._trigger("beforeStop", event, this._uiHash());
+		}
+
+		//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
+		this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
+
+		if ( !this.cancelHelperRemoval ) {
+			if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {
+				this.helper.remove();
+			}
+			this.helper = null;
+		}
+
+		if(!noPropagation) {
+			for (i=0; i < delayedTriggers.length; i++) {
+				delayedTriggers[i].call(this, event);
+			} //Trigger all delayed events
+			this._trigger("stop", event, this._uiHash());
+		}
+
+		this.fromOutside = false;
+		return !this.cancelHelperRemoval;
+
+	},
+
+	_trigger: function() {
+		if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
+			this.cancel();
+		}
+	},
+
+	_uiHash: function(_inst) {
+		var inst = _inst || this;
+		return {
+			helper: inst.helper,
+			placeholder: inst.placeholder || $([]),
+			position: inst.position,
+			originalPosition: inst.originalPosition,
+			offset: inst.positionAbs,
+			item: inst.currentItem,
+			sender: _inst ? _inst.element : null
+		};
+	}
+
+});
+
+
+/*!
+ * jQuery UI Accordion 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/accordion/
+ */
+
+
+var accordion = $.widget( "ui.accordion", {
+	version: "1.11.4",
+	options: {
+		active: 0,
+		animate: {},
+		collapsible: false,
+		event: "click",
+		header: "> li > :first-child,> :not(li):even",
+		heightStyle: "auto",
+		icons: {
+			activeHeader: "ui-icon-triangle-1-s",
+			header: "ui-icon-triangle-1-e"
+		},
+
+		// callbacks
+		activate: null,
+		beforeActivate: null
+	},
+
+	hideProps: {
+		borderTopWidth: "hide",
+		borderBottomWidth: "hide",
+		paddingTop: "hide",
+		paddingBottom: "hide",
+		height: "hide"
+	},
+
+	showProps: {
+		borderTopWidth: "show",
+		borderBottomWidth: "show",
+		paddingTop: "show",
+		paddingBottom: "show",
+		height: "show"
+	},
+
+	_create: function() {
+		var options = this.options;
+		this.prevShow = this.prevHide = $();
+		this.element.addClass( "ui-accordion ui-widget ui-helper-reset" )
+			// ARIA
+			.attr( "role", "tablist" );
+
+		// don't allow collapsible: false and active: false / null
+		if ( !options.collapsible && (options.active === false || options.active == null) ) {
+			options.active = 0;
+		}
+
+		this._processPanels();
+		// handle negative values
+		if ( options.active < 0 ) {
+			options.active += this.headers.length;
+		}
+		this._refresh();
+	},
+
+	_getCreateEventData: function() {
+		return {
+			header: this.active,
+			panel: !this.active.length ? $() : this.active.next()
+		};
+	},
+
+	_createIcons: function() {
+		var icons = this.options.icons;
+		if ( icons ) {
+			$( "<span>" )
+				.addClass( "ui-accordion-header-icon ui-icon " + icons.header )
+				.prependTo( this.headers );
+			this.active.children( ".ui-accordion-header-icon" )
+				.removeClass( icons.header )
+				.addClass( icons.activeHeader );
+			this.headers.addClass( "ui-accordion-icons" );
+		}
+	},
+
+	_destroyIcons: function() {
+		this.headers
+			.removeClass( "ui-accordion-icons" )
+			.children( ".ui-accordion-header-icon" )
+				.remove();
+	},
+
+	_destroy: function() {
+		var contents;
+
+		// clean up main element
+		this.element
+			.removeClass( "ui-accordion ui-widget ui-helper-reset" )
+			.removeAttr( "role" );
+
+		// clean up headers
+		this.headers
+			.removeClass( "ui-accordion-header ui-accordion-header-active ui-state-default " +
+				"ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
+			.removeAttr( "role" )
+			.removeAttr( "aria-expanded" )
+			.removeAttr( "aria-selected" )
+			.removeAttr( "aria-controls" )
+			.removeAttr( "tabIndex" )
+			.removeUniqueId();
+
+		this._destroyIcons();
+
+		// clean up content panels
+		contents = this.headers.next()
+			.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom " +
+				"ui-accordion-content ui-accordion-content-active ui-state-disabled" )
+			.css( "display", "" )
+			.removeAttr( "role" )
+			.removeAttr( "aria-hidden" )
+			.removeAttr( "aria-labelledby" )
+			.removeUniqueId();
+
+		if ( this.options.heightStyle !== "content" ) {
+			contents.css( "height", "" );
+		}
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "active" ) {
+			// _activate() will handle invalid values and update this.options
+			this._activate( value );
+			return;
+		}
+
+		if ( key === "event" ) {
+			if ( this.options.event ) {
+				this._off( this.headers, this.options.event );
+			}
+			this._setupEvents( value );
+		}
+
+		this._super( key, value );
+
+		// setting collapsible: false while collapsed; open first panel
+		if ( key === "collapsible" && !value && this.options.active === false ) {
+			this._activate( 0 );
+		}
+
+		if ( key === "icons" ) {
+			this._destroyIcons();
+			if ( value ) {
+				this._createIcons();
+			}
+		}
+
+		// #5332 - opacity doesn't cascade to positioned elements in IE
+		// so we need to add the disabled class to the headers and panels
+		if ( key === "disabled" ) {
+			this.element
+				.toggleClass( "ui-state-disabled", !!value )
+				.attr( "aria-disabled", value );
+			this.headers.add( this.headers.next() )
+				.toggleClass( "ui-state-disabled", !!value );
+		}
+	},
+
+	_keydown: function( event ) {
+		if ( event.altKey || event.ctrlKey ) {
+			return;
+		}
+
+		var keyCode = $.ui.keyCode,
+			length = this.headers.length,
+			currentIndex = this.headers.index( event.target ),
+			toFocus = false;
+
+		switch ( event.keyCode ) {
+			case keyCode.RIGHT:
+			case keyCode.DOWN:
+				toFocus = this.headers[ ( currentIndex + 1 ) % length ];
+				break;
+			case keyCode.LEFT:
+			case keyCode.UP:
+				toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
+				break;
+			case keyCode.SPACE:
+			case keyCode.ENTER:
+				this._eventHandler( event );
+				break;
+			case keyCode.HOME:
+				toFocus = this.headers[ 0 ];
+				break;
+			case keyCode.END:
+				toFocus = this.headers[ length - 1 ];
+				break;
+		}
+
+		if ( toFocus ) {
+			$( event.target ).attr( "tabIndex", -1 );
+			$( toFocus ).attr( "tabIndex", 0 );
+			toFocus.focus();
+			event.preventDefault();
+		}
+	},
+
+	_panelKeyDown: function( event ) {
+		if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
+			$( event.currentTarget ).prev().focus();
+		}
+	},
+
+	refresh: function() {
+		var options = this.options;
+		this._processPanels();
+
+		// was collapsed or no panel
+		if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) {
+			options.active = false;
+			this.active = $();
+		// active false only when collapsible is true
+		} else if ( options.active === false ) {
+			this._activate( 0 );
+		// was active, but active panel is gone
+		} else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
+			// all remaining panel are disabled
+			if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) {
+				options.active = false;
+				this.active = $();
+			// activate previous panel
+			} else {
+				this._activate( Math.max( 0, options.active - 1 ) );
+			}
+		// was active, active panel still exists
+		} else {
+			// make sure active index is correct
+			options.active = this.headers.index( this.active );
+		}
+
+		this._destroyIcons();
+
+		this._refresh();
+	},
+
+	_processPanels: function() {
+		var prevHeaders = this.headers,
+			prevPanels = this.panels;
+
+		this.headers = this.element.find( this.options.header )
+			.addClass( "ui-accordion-header ui-state-default ui-corner-all" );
+
+		this.panels = this.headers.next()
+			.addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
+			.filter( ":not(.ui-accordion-content-active)" )
+			.hide();
+
+		// Avoid memory leaks (#10056)
+		if ( prevPanels ) {
+			this._off( prevHeaders.not( this.headers ) );
+			this._off( prevPanels.not( this.panels ) );
+		}
+	},
+
+	_refresh: function() {
+		var maxHeight,
+			options = this.options,
+			heightStyle = options.heightStyle,
+			parent = this.element.parent();
+
+		this.active = this._findActive( options.active )
+			.addClass( "ui-accordion-header-active ui-state-active ui-corner-top" )
+			.removeClass( "ui-corner-all" );
+		this.active.next()
+			.addClass( "ui-accordion-content-active" )
+			.show();
+
+		this.headers
+			.attr( "role", "tab" )
+			.each(function() {
+				var header = $( this ),
+					headerId = header.uniqueId().attr( "id" ),
+					panel = header.next(),
+					panelId = panel.uniqueId().attr( "id" );
+				header.attr( "aria-controls", panelId );
+				panel.attr( "aria-labelledby", headerId );
+			})
+			.next()
+				.attr( "role", "tabpanel" );
+
+		this.headers
+			.not( this.active )
+			.attr({
+				"aria-selected": "false",
+				"aria-expanded": "false",
+				tabIndex: -1
+			})
+			.next()
+				.attr({
+					"aria-hidden": "true"
+				})
+				.hide();
+
+		// make sure at least one header is in the tab order
+		if ( !this.active.length ) {
+			this.headers.eq( 0 ).attr( "tabIndex", 0 );
+		} else {
+			this.active.attr({
+				"aria-selected": "true",
+				"aria-expanded": "true",
+				tabIndex: 0
+			})
+			.next()
+				.attr({
+					"aria-hidden": "false"
+				});
+		}
+
+		this._createIcons();
+
+		this._setupEvents( options.event );
+
+		if ( heightStyle === "fill" ) {
+			maxHeight = parent.height();
+			this.element.siblings( ":visible" ).each(function() {
+				var elem = $( this ),
+					position = elem.css( "position" );
+
+				if ( position === "absolute" || position === "fixed" ) {
+					return;
+				}
+				maxHeight -= elem.outerHeight( true );
+			});
+
+			this.headers.each(function() {
+				maxHeight -= $( this ).outerHeight( true );
+			});
+
+			this.headers.next()
+				.each(function() {
+					$( this ).height( Math.max( 0, maxHeight -
+						$( this ).innerHeight() + $( this ).height() ) );
+				})
+				.css( "overflow", "auto" );
+		} else if ( heightStyle === "auto" ) {
+			maxHeight = 0;
+			this.headers.next()
+				.each(function() {
+					maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
+				})
+				.height( maxHeight );
+		}
+	},
+
+	_activate: function( index ) {
+		var active = this._findActive( index )[ 0 ];
+
+		// trying to activate the already active panel
+		if ( active === this.active[ 0 ] ) {
+			return;
+		}
+
+		// trying to collapse, simulate a click on the currently active header
+		active = active || this.active[ 0 ];
+
+		this._eventHandler({
+			target: active,
+			currentTarget: active,
+			preventDefault: $.noop
+		});
+	},
+
+	_findActive: function( selector ) {
+		return typeof selector === "number" ? this.headers.eq( selector ) : $();
+	},
+
+	_setupEvents: function( event ) {
+		var events = {
+			keydown: "_keydown"
+		};
+		if ( event ) {
+			$.each( event.split( " " ), function( index, eventName ) {
+				events[ eventName ] = "_eventHandler";
+			});
+		}
+
+		this._off( this.headers.add( this.headers.next() ) );
+		this._on( this.headers, events );
+		this._on( this.headers.next(), { keydown: "_panelKeyDown" });
+		this._hoverable( this.headers );
+		this._focusable( this.headers );
+	},
+
+	_eventHandler: function( event ) {
+		var options = this.options,
+			active = this.active,
+			clicked = $( event.currentTarget ),
+			clickedIsActive = clicked[ 0 ] === active[ 0 ],
+			collapsing = clickedIsActive && options.collapsible,
+			toShow = collapsing ? $() : clicked.next(),
+			toHide = active.next(),
+			eventData = {
+				oldHeader: active,
+				oldPanel: toHide,
+				newHeader: collapsing ? $() : clicked,
+				newPanel: toShow
+			};
+
+		event.preventDefault();
+
+		if (
+				// click on active header, but not collapsible
+				( clickedIsActive && !options.collapsible ) ||
+				// allow canceling activation
+				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
+			return;
+		}
+
+		options.active = collapsing ? false : this.headers.index( clicked );
+
+		// when the call to ._toggle() comes after the class changes
+		// it causes a very odd bug in IE 8 (see #6720)
+		this.active = clickedIsActive ? $() : clicked;
+		this._toggle( eventData );
+
+		// switch classes
+		// corner classes on the previously active header stay after the animation
+		active.removeClass( "ui-accordion-header-active ui-state-active" );
+		if ( options.icons ) {
+			active.children( ".ui-accordion-header-icon" )
+				.removeClass( options.icons.activeHeader )
+				.addClass( options.icons.header );
+		}
+
+		if ( !clickedIsActive ) {
+			clicked
+				.removeClass( "ui-corner-all" )
+				.addClass( "ui-accordion-header-active ui-state-active ui-corner-top" );
+			if ( options.icons ) {
+				clicked.children( ".ui-accordion-header-icon" )
+					.removeClass( options.icons.header )
+					.addClass( options.icons.activeHeader );
+			}
+
+			clicked
+				.next()
+				.addClass( "ui-accordion-content-active" );
+		}
+	},
+
+	_toggle: function( data ) {
+		var toShow = data.newPanel,
+			toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
+
+		// handle activating a panel during the animation for another activation
+		this.prevShow.add( this.prevHide ).stop( true, true );
+		this.prevShow = toShow;
+		this.prevHide = toHide;
+
+		if ( this.options.animate ) {
+			this._animate( toShow, toHide, data );
+		} else {
+			toHide.hide();
+			toShow.show();
+			this._toggleComplete( data );
+		}
+
+		toHide.attr({
+			"aria-hidden": "true"
+		});
+		toHide.prev().attr({
+			"aria-selected": "false",
+			"aria-expanded": "false"
+		});
+		// if we're switching panels, remove the old header from the tab order
+		// if we're opening from collapsed state, remove the previous header from the tab order
+		// if we're collapsing, then keep the collapsing header in the tab order
+		if ( toShow.length && toHide.length ) {
+			toHide.prev().attr({
+				"tabIndex": -1,
+				"aria-expanded": "false"
+			});
+		} else if ( toShow.length ) {
+			this.headers.filter(function() {
+				return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0;
+			})
+			.attr( "tabIndex", -1 );
+		}
+
+		toShow
+			.attr( "aria-hidden", "false" )
+			.prev()
+				.attr({
+					"aria-selected": "true",
+					"aria-expanded": "true",
+					tabIndex: 0
+				});
+	},
+
+	_animate: function( toShow, toHide, data ) {
+		var total, easing, duration,
+			that = this,
+			adjust = 0,
+			boxSizing = toShow.css( "box-sizing" ),
+			down = toShow.length &&
+				( !toHide.length || ( toShow.index() < toHide.index() ) ),
+			animate = this.options.animate || {},
+			options = down && animate.down || animate,
+			complete = function() {
+				that._toggleComplete( data );
+			};
+
+		if ( typeof options === "number" ) {
+			duration = options;
+		}
+		if ( typeof options === "string" ) {
+			easing = options;
+		}
+		// fall back from options to animation in case of partial down settings
+		easing = easing || options.easing || animate.easing;
+		duration = duration || options.duration || animate.duration;
+
+		if ( !toHide.length ) {
+			return toShow.animate( this.showProps, duration, easing, complete );
+		}
+		if ( !toShow.length ) {
+			return toHide.animate( this.hideProps, duration, easing, complete );
+		}
+
+		total = toShow.show().outerHeight();
+		toHide.animate( this.hideProps, {
+			duration: duration,
+			easing: easing,
+			step: function( now, fx ) {
+				fx.now = Math.round( now );
+			}
+		});
+		toShow
+			.hide()
+			.animate( this.showProps, {
+				duration: duration,
+				easing: easing,
+				complete: complete,
+				step: function( now, fx ) {
+					fx.now = Math.round( now );
+					if ( fx.prop !== "height" ) {
+						if ( boxSizing === "content-box" ) {
+							adjust += fx.now;
+						}
+					} else if ( that.options.heightStyle !== "content" ) {
+						fx.now = Math.round( total - toHide.outerHeight() - adjust );
+						adjust = 0;
+					}
+				}
+			});
+	},
+
+	_toggleComplete: function( data ) {
+		var toHide = data.oldPanel;
+
+		toHide
+			.removeClass( "ui-accordion-content-active" )
+			.prev()
+				.removeClass( "ui-corner-top" )
+				.addClass( "ui-corner-all" );
+
+		// Work around for rendering bug in IE (#5421)
+		if ( toHide.length ) {
+			toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className;
+		}
+		this._trigger( "activate", null, data );
+	}
+});
+
+
+/*!
+ * jQuery UI Menu 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/menu/
+ */
+
+
+var menu = $.widget( "ui.menu", {
+	version: "1.11.4",
+	defaultElement: "<ul>",
+	delay: 300,
+	options: {
+		icons: {
+			submenu: "ui-icon-carat-1-e"
+		},
+		items: "> *",
+		menus: "ul",
+		position: {
+			my: "left-1 top",
+			at: "right top"
+		},
+		role: "menu",
+
+		// callbacks
+		blur: null,
+		focus: null,
+		select: null
+	},
+
+	_create: function() {
+		this.activeMenu = this.element;
+
+		// Flag used to prevent firing of the click handler
+		// as the event bubbles up through nested menus
+		this.mouseHandled = false;
+		this.element
+			.uniqueId()
+			.addClass( "ui-menu ui-widget ui-widget-content" )
+			.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length )
+			.attr({
+				role: this.options.role,
+				tabIndex: 0
+			});
+
+		if ( this.options.disabled ) {
+			this.element
+				.addClass( "ui-state-disabled" )
+				.attr( "aria-disabled", "true" );
+		}
+
+		this._on({
+			// Prevent focus from sticking to links inside menu after clicking
+			// them (focus should always stay on UL during navigation).
+			"mousedown .ui-menu-item": function( event ) {
+				event.preventDefault();
+			},
+			"click .ui-menu-item": function( event ) {
+				var target = $( event.target );
+				if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
+					this.select( event );
+
+					// Only set the mouseHandled flag if the event will bubble, see #9469.
+					if ( !event.isPropagationStopped() ) {
+						this.mouseHandled = true;
+					}
+
+					// Open submenu on click
+					if ( target.has( ".ui-menu" ).length ) {
+						this.expand( event );
+					} else if ( !this.element.is( ":focus" ) && $( this.document[ 0 ].activeElement ).closest( ".ui-menu" ).length ) {
+
+						// Redirect focus to the menu
+						this.element.trigger( "focus", [ true ] );
+
+						// If the active item is on the top level, let it stay active.
+						// Otherwise, blur the active item since it is no longer visible.
+						if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
+							clearTimeout( this.timer );
+						}
+					}
+				}
+			},
+			"mouseenter .ui-menu-item": function( event ) {
+				// Ignore mouse events while typeahead is active, see #10458.
+				// Prevents focusing the wrong item when typeahead causes a scroll while the mouse
+				// is over an item in the menu
+				if ( this.previousFilter ) {
+					return;
+				}
+				var target = $( event.currentTarget );
+				// Remove ui-state-active class from siblings of the newly focused menu item
+				// to avoid a jump caused by adjacent elements both having a class with a border
+				target.siblings( ".ui-state-active" ).removeClass( "ui-state-active" );
+				this.focus( event, target );
+			},
+			mouseleave: "collapseAll",
+			"mouseleave .ui-menu": "collapseAll",
+			focus: function( event, keepActiveItem ) {
+				// If there's already an active item, keep it active
+				// If not, activate the first item
+				var item = this.active || this.element.find( this.options.items ).eq( 0 );
+
+				if ( !keepActiveItem ) {
+					this.focus( event, item );
+				}
+			},
+			blur: function( event ) {
+				this._delay(function() {
+					if ( !$.contains( this.element[0], this.document[0].activeElement ) ) {
+						this.collapseAll( event );
+					}
+				});
+			},
+			keydown: "_keydown"
+		});
+
+		this.refresh();
+
+		// Clicks outside of a menu collapse any open menus
+		this._on( this.document, {
+			click: function( event ) {
+				if ( this._closeOnDocumentClick( event ) ) {
+					this.collapseAll( event );
+				}
+
+				// Reset the mouseHandled flag
+				this.mouseHandled = false;
+			}
+		});
+	},
+
+	_destroy: function() {
+		// Destroy (sub)menus
+		this.element
+			.removeAttr( "aria-activedescendant" )
+			.find( ".ui-menu" ).addBack()
+				.removeClass( "ui-menu ui-widget ui-widget-content ui-menu-icons ui-front" )
+				.removeAttr( "role" )
+				.removeAttr( "tabIndex" )
+				.removeAttr( "aria-labelledby" )
+				.removeAttr( "aria-expanded" )
+				.removeAttr( "aria-hidden" )
+				.removeAttr( "aria-disabled" )
+				.removeUniqueId()
+				.show();
+
+		// Destroy menu items
+		this.element.find( ".ui-menu-item" )
+			.removeClass( "ui-menu-item" )
+			.removeAttr( "role" )
+			.removeAttr( "aria-disabled" )
+			.removeUniqueId()
+			.removeClass( "ui-state-hover" )
+			.removeAttr( "tabIndex" )
+			.removeAttr( "role" )
+			.removeAttr( "aria-haspopup" )
+			.children().each( function() {
+				var elem = $( this );
+				if ( elem.data( "ui-menu-submenu-carat" ) ) {
+					elem.remove();
+				}
+			});
+
+		// Destroy menu dividers
+		this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
+	},
+
+	_keydown: function( event ) {
+		var match, prev, character, skip,
+			preventDefault = true;
+
+		switch ( event.keyCode ) {
+		case $.ui.keyCode.PAGE_UP:
+			this.previousPage( event );
+			break;
+		case $.ui.keyCode.PAGE_DOWN:
+			this.nextPage( event );
+			break;
+		case $.ui.keyCode.HOME:
+			this._move( "first", "first", event );
+			break;
+		case $.ui.keyCode.END:
+			this._move( "last", "last", event );
+			break;
+		case $.ui.keyCode.UP:
+			this.previous( event );
+			break;
+		case $.ui.keyCode.DOWN:
+			this.next( event );
+			break;
+		case $.ui.keyCode.LEFT:
+			this.collapse( event );
+			break;
+		case $.ui.keyCode.RIGHT:
+			if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
+				this.expand( event );
+			}
+			break;
+		case $.ui.keyCode.ENTER:
+		case $.ui.keyCode.SPACE:
+			this._activate( event );
+			break;
+		case $.ui.keyCode.ESCAPE:
+			this.collapse( event );
+			break;
+		default:
+			preventDefault = false;
+			prev = this.previousFilter || "";
+			character = String.fromCharCode( event.keyCode );
+			skip = false;
+
+			clearTimeout( this.filterTimer );
+
+			if ( character === prev ) {
+				skip = true;
+			} else {
+				character = prev + character;
+			}
+
+			match = this._filterMenuItems( character );
+			match = skip && match.index( this.active.next() ) !== -1 ?
+				this.active.nextAll( ".ui-menu-item" ) :
+				match;
+
+			// If no matches on the current filter, reset to the last character pressed
+			// to move down the menu to the first item that starts with that character
+			if ( !match.length ) {
+				character = String.fromCharCode( event.keyCode );
+				match = this._filterMenuItems( character );
+			}
+
+			if ( match.length ) {
+				this.focus( event, match );
+				this.previousFilter = character;
+				this.filterTimer = this._delay(function() {
+					delete this.previousFilter;
+				}, 1000 );
+			} else {
+				delete this.previousFilter;
+			}
+		}
+
+		if ( preventDefault ) {
+			event.preventDefault();
+		}
+	},
+
+	_activate: function( event ) {
+		if ( !this.active.is( ".ui-state-disabled" ) ) {
+			if ( this.active.is( "[aria-haspopup='true']" ) ) {
+				this.expand( event );
+			} else {
+				this.select( event );
+			}
+		}
+	},
+
+	refresh: function() {
+		var menus, items,
+			that = this,
+			icon = this.options.icons.submenu,
+			submenus = this.element.find( this.options.menus );
+
+		this.element.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length );
+
+		// Initialize nested menus
+		submenus.filter( ":not(.ui-menu)" )
+			.addClass( "ui-menu ui-widget ui-widget-content ui-front" )
+			.hide()
+			.attr({
+				role: this.options.role,
+				"aria-hidden": "true",
+				"aria-expanded": "false"
+			})
+			.each(function() {
+				var menu = $( this ),
+					item = menu.parent(),
+					submenuCarat = $( "<span>" )
+						.addClass( "ui-menu-icon ui-icon " + icon )
+						.data( "ui-menu-submenu-carat", true );
+
+				item
+					.attr( "aria-haspopup", "true" )
+					.prepend( submenuCarat );
+				menu.attr( "aria-labelledby", item.attr( "id" ) );
+			});
+
+		menus = submenus.add( this.element );
+		items = menus.find( this.options.items );
+
+		// Initialize menu-items containing spaces and/or dashes only as dividers
+		items.not( ".ui-menu-item" ).each(function() {
+			var item = $( this );
+			if ( that._isDivider( item ) ) {
+				item.addClass( "ui-widget-content ui-menu-divider" );
+			}
+		});
+
+		// Don't refresh list items that are already adapted
+		items.not( ".ui-menu-item, .ui-menu-divider" )
+			.addClass( "ui-menu-item" )
+			.uniqueId()
+			.attr({
+				tabIndex: -1,
+				role: this._itemRole()
+			});
+
+		// Add aria-disabled attribute to any disabled menu item
+		items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
+
+		// If the active item has been removed, blur the menu
+		if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
+			this.blur();
+		}
+	},
+
+	_itemRole: function() {
+		return {
+			menu: "menuitem",
+			listbox: "option"
+		}[ this.options.role ];
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "icons" ) {
+			this.element.find( ".ui-menu-icon" )
+				.removeClass( this.options.icons.submenu )
+				.addClass( value.submenu );
+		}
+		if ( key === "disabled" ) {
+			this.element
+				.toggleClass( "ui-state-disabled", !!value )
+				.attr( "aria-disabled", value );
+		}
+		this._super( key, value );
+	},
+
+	focus: function( event, item ) {
+		var nested, focused;
+		this.blur( event, event && event.type === "focus" );
+
+		this._scrollIntoView( item );
+
+		this.active = item.first();
+		focused = this.active.addClass( "ui-state-focus" ).removeClass( "ui-state-active" );
+		// Only update aria-activedescendant if there's a role
+		// otherwise we assume focus is managed elsewhere
+		if ( this.options.role ) {
+			this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
+		}
+
+		// Highlight active parent menu item, if any
+		this.active
+			.parent()
+			.closest( ".ui-menu-item" )
+			.addClass( "ui-state-active" );
+
+		if ( event && event.type === "keydown" ) {
+			this._close();
+		} else {
+			this.timer = this._delay(function() {
+				this._close();
+			}, this.delay );
+		}
+
+		nested = item.children( ".ui-menu" );
+		if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {
+			this._startOpening(nested);
+		}
+		this.activeMenu = item.parent();
+
+		this._trigger( "focus", event, { item: item } );
+	},
+
+	_scrollIntoView: function( item ) {
+		var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
+		if ( this._hasScroll() ) {
+			borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0;
+			paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0;
+			offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
+			scroll = this.activeMenu.scrollTop();
+			elementHeight = this.activeMenu.height();
+			itemHeight = item.outerHeight();
+
+			if ( offset < 0 ) {
+				this.activeMenu.scrollTop( scroll + offset );
+			} else if ( offset + itemHeight > elementHeight ) {
+				this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
+			}
+		}
+	},
+
+	blur: function( event, fromFocus ) {
+		if ( !fromFocus ) {
+			clearTimeout( this.timer );
+		}
+
+		if ( !this.active ) {
+			return;
+		}
+
+		this.active.removeClass( "ui-state-focus" );
+		this.active = null;
+
+		this._trigger( "blur", event, { item: this.active } );
+	},
+
+	_startOpening: function( submenu ) {
+		clearTimeout( this.timer );
+
+		// Don't open if already open fixes a Firefox bug that caused a .5 pixel
+		// shift in the submenu position when mousing over the carat icon
+		if ( submenu.attr( "aria-hidden" ) !== "true" ) {
+			return;
+		}
+
+		this.timer = this._delay(function() {
+			this._close();
+			this._open( submenu );
+		}, this.delay );
+	},
+
+	_open: function( submenu ) {
+		var position = $.extend({
+			of: this.active
+		}, this.options.position );
+
+		clearTimeout( this.timer );
+		this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
+			.hide()
+			.attr( "aria-hidden", "true" );
+
+		submenu
+			.show()
+			.removeAttr( "aria-hidden" )
+			.attr( "aria-expanded", "true" )
+			.position( position );
+	},
+
+	collapseAll: function( event, all ) {
+		clearTimeout( this.timer );
+		this.timer = this._delay(function() {
+			// If we were passed an event, look for the submenu that contains the event
+			var currentMenu = all ? this.element :
+				$( event && event.target ).closest( this.element.find( ".ui-menu" ) );
+
+			// If we found no valid submenu ancestor, use the main menu to close all sub menus anyway
+			if ( !currentMenu.length ) {
+				currentMenu = this.element;
+			}
+
+			this._close( currentMenu );
+
+			this.blur( event );
+			this.activeMenu = currentMenu;
+		}, this.delay );
+	},
+
+	// With no arguments, closes the currently active menu - if nothing is active
+	// it closes all menus.  If passed an argument, it will search for menus BELOW
+	_close: function( startMenu ) {
+		if ( !startMenu ) {
+			startMenu = this.active ? this.active.parent() : this.element;
+		}
+
+		startMenu
+			.find( ".ui-menu" )
+				.hide()
+				.attr( "aria-hidden", "true" )
+				.attr( "aria-expanded", "false" )
+			.end()
+			.find( ".ui-state-active" ).not( ".ui-state-focus" )
+				.removeClass( "ui-state-active" );
+	},
+
+	_closeOnDocumentClick: function( event ) {
+		return !$( event.target ).closest( ".ui-menu" ).length;
+	},
+
+	_isDivider: function( item ) {
+
+		// Match hyphen, em dash, en dash
+		return !/[^\-\u2014\u2013\s]/.test( item.text() );
+	},
+
+	collapse: function( event ) {
+		var newItem = this.active &&
+			this.active.parent().closest( ".ui-menu-item", this.element );
+		if ( newItem && newItem.length ) {
+			this._close();
+			this.focus( event, newItem );
+		}
+	},
+
+	expand: function( event ) {
+		var newItem = this.active &&
+			this.active
+				.children( ".ui-menu " )
+				.find( this.options.items )
+				.first();
+
+		if ( newItem && newItem.length ) {
+			this._open( newItem.parent() );
+
+			// Delay so Firefox will not hide activedescendant change in expanding submenu from AT
+			this._delay(function() {
+				this.focus( event, newItem );
+			});
+		}
+	},
+
+	next: function( event ) {
+		this._move( "next", "first", event );
+	},
+
+	previous: function( event ) {
+		this._move( "prev", "last", event );
+	},
+
+	isFirstItem: function() {
+		return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
+	},
+
+	isLastItem: function() {
+		return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
+	},
+
+	_move: function( direction, filter, event ) {
+		var next;
+		if ( this.active ) {
+			if ( direction === "first" || direction === "last" ) {
+				next = this.active
+					[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
+					.eq( -1 );
+			} else {
+				next = this.active
+					[ direction + "All" ]( ".ui-menu-item" )
+					.eq( 0 );
+			}
+		}
+		if ( !next || !next.length || !this.active ) {
+			next = this.activeMenu.find( this.options.items )[ filter ]();
+		}
+
+		this.focus( event, next );
+	},
+
+	nextPage: function( event ) {
+		var item, base, height;
+
+		if ( !this.active ) {
+			this.next( event );
+			return;
+		}
+		if ( this.isLastItem() ) {
+			return;
+		}
+		if ( this._hasScroll() ) {
+			base = this.active.offset().top;
+			height = this.element.height();
+			this.active.nextAll( ".ui-menu-item" ).each(function() {
+				item = $( this );
+				return item.offset().top - base - height < 0;
+			});
+
+			this.focus( event, item );
+		} else {
+			this.focus( event, this.activeMenu.find( this.options.items )
+				[ !this.active ? "first" : "last" ]() );
+		}
+	},
+
+	previousPage: function( event ) {
+		var item, base, height;
+		if ( !this.active ) {
+			this.next( event );
+			return;
+		}
+		if ( this.isFirstItem() ) {
+			return;
+		}
+		if ( this._hasScroll() ) {
+			base = this.active.offset().top;
+			height = this.element.height();
+			this.active.prevAll( ".ui-menu-item" ).each(function() {
+				item = $( this );
+				return item.offset().top - base + height > 0;
+			});
+
+			this.focus( event, item );
+		} else {
+			this.focus( event, this.activeMenu.find( this.options.items ).first() );
+		}
+	},
+
+	_hasScroll: function() {
+		return this.element.outerHeight() < this.element.prop( "scrollHeight" );
+	},
+
+	select: function( event ) {
+		// TODO: It should never be possible to not have an active item at this
+		// point, but the tests don't trigger mouseenter before click.
+		this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
+		var ui = { item: this.active };
+		if ( !this.active.has( ".ui-menu" ).length ) {
+			this.collapseAll( event, true );
+		}
+		this._trigger( "select", event, ui );
+	},
+
+	_filterMenuItems: function(character) {
+		var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ),
+			regex = new RegExp( "^" + escapedCharacter, "i" );
+
+		return this.activeMenu
+			.find( this.options.items )
+
+			// Only match on items, not dividers or other content (#10571)
+			.filter( ".ui-menu-item" )
+			.filter(function() {
+				return regex.test( $.trim( $( this ).text() ) );
+			});
+	}
+});
+
+
+/*!
+ * jQuery UI Autocomplete 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/autocomplete/
+ */
+
+
+$.widget( "ui.autocomplete", {
+	version: "1.11.4",
+	defaultElement: "<input>",
+	options: {
+		appendTo: null,
+		autoFocus: false,
+		delay: 300,
+		minLength: 1,
+		position: {
+			my: "left top",
+			at: "left bottom",
+			collision: "none"
+		},
+		source: null,
+
+		// callbacks
+		change: null,
+		close: null,
+		focus: null,
+		open: null,
+		response: null,
+		search: null,
+		select: null
+	},
+
+	requestIndex: 0,
+	pending: 0,
+
+	_create: function() {
+		// Some browsers only repeat keydown events, not keypress events,
+		// so we use the suppressKeyPress flag to determine if we've already
+		// handled the keydown event. #7269
+		// Unfortunately the code for & in keypress is the same as the up arrow,
+		// so we use the suppressKeyPressRepeat flag to avoid handling keypress
+		// events when we know the keydown event was used to modify the
+		// search term. #7799
+		var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
+			nodeName = this.element[ 0 ].nodeName.toLowerCase(),
+			isTextarea = nodeName === "textarea",
+			isInput = nodeName === "input";
+
+		this.isMultiLine =
+			// Textareas are always multi-line
+			isTextarea ? true :
+			// Inputs are always single-line, even if inside a contentEditable element
+			// IE also treats inputs as contentEditable
+			isInput ? false :
+			// All other element types are determined by whether or not they're contentEditable
+			this.element.prop( "isContentEditable" );
+
+		this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
+		this.isNewMenu = true;
+
+		this.element
+			.addClass( "ui-autocomplete-input" )
+			.attr( "autocomplete", "off" );
+
+		this._on( this.element, {
+			keydown: function( event ) {
+				if ( this.element.prop( "readOnly" ) ) {
+					suppressKeyPress = true;
+					suppressInput = true;
+					suppressKeyPressRepeat = true;
+					return;
+				}
+
+				suppressKeyPress = false;
+				suppressInput = false;
+				suppressKeyPressRepeat = false;
+				var keyCode = $.ui.keyCode;
+				switch ( event.keyCode ) {
+				case keyCode.PAGE_UP:
+					suppressKeyPress = true;
+					this._move( "previousPage", event );
+					break;
+				case keyCode.PAGE_DOWN:
+					suppressKeyPress = true;
+					this._move( "nextPage", event );
+					break;
+				case keyCode.UP:
+					suppressKeyPress = true;
+					this._keyEvent( "previous", event );
+					break;
+				case keyCode.DOWN:
+					suppressKeyPress = true;
+					this._keyEvent( "next", event );
+					break;
+				case keyCode.ENTER:
+					// when menu is open and has focus
+					if ( this.menu.active ) {
+						// #6055 - Opera still allows the keypress to occur
+						// which causes forms to submit
+						suppressKeyPress = true;
+						event.preventDefault();
+						this.menu.select( event );
+					}
+					break;
+				case keyCode.TAB:
+					if ( this.menu.active ) {
+						this.menu.select( event );
+					}
+					break;
+				case keyCode.ESCAPE:
+					if ( this.menu.element.is( ":visible" ) ) {
+						if ( !this.isMultiLine ) {
+							this._value( this.term );
+						}
+						this.close( event );
+						// Different browsers have different default behavior for escape
+						// Single press can mean undo or clear
+						// Double press in IE means clear the whole form
+						event.preventDefault();
+					}
+					break;
+				default:
+					suppressKeyPressRepeat = true;
+					// search timeout should be triggered before the input value is changed
+					this._searchTimeout( event );
+					break;
+				}
+			},
+			keypress: function( event ) {
+				if ( suppressKeyPress ) {
+					suppressKeyPress = false;
+					if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
+						event.preventDefault();
+					}
+					return;
+				}
+				if ( suppressKeyPressRepeat ) {
+					return;
+				}
+
+				// replicate some key handlers to allow them to repeat in Firefox and Opera
+				var keyCode = $.ui.keyCode;
+				switch ( event.keyCode ) {
+				case keyCode.PAGE_UP:
+					this._move( "previousPage", event );
+					break;
+				case keyCode.PAGE_DOWN:
+					this._move( "nextPage", event );
+					break;
+				case keyCode.UP:
+					this._keyEvent( "previous", event );
+					break;
+				case keyCode.DOWN:
+					this._keyEvent( "next", event );
+					break;
+				}
+			},
+			input: function( event ) {
+				if ( suppressInput ) {
+					suppressInput = false;
+					event.preventDefault();
+					return;
+				}
+				this._searchTimeout( event );
+			},
+			focus: function() {
+				this.selectedItem = null;
+				this.previous = this._value();
+			},
+			blur: function( event ) {
+				if ( this.cancelBlur ) {
+					delete this.cancelBlur;
+					return;
+				}
+
+				clearTimeout( this.searching );
+				this.close( event );
+				this._change( event );
+			}
+		});
+
+		this._initSource();
+		this.menu = $( "<ul>" )
+			.addClass( "ui-autocomplete ui-front" )
+			.appendTo( this._appendTo() )
+			.menu({
+				// disable ARIA support, the live region takes care of that
+				role: null
+			})
+			.hide()
+			.menu( "instance" );
+
+		this._on( this.menu.element, {
+			mousedown: function( event ) {
+				// prevent moving focus out of the text field
+				event.preventDefault();
+
+				// IE doesn't prevent moving focus even with event.preventDefault()
+				// so we set a flag to know when we should ignore the blur event
+				this.cancelBlur = true;
+				this._delay(function() {
+					delete this.cancelBlur;
+				});
+
+				// clicking on the scrollbar causes focus to shift to the body
+				// but we can't detect a mouseup or a click immediately afterward
+				// so we have to track the next mousedown and close the menu if
+				// the user clicks somewhere outside of the autocomplete
+				var menuElement = this.menu.element[ 0 ];
+				if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
+					this._delay(function() {
+						var that = this;
+						this.document.one( "mousedown", function( event ) {
+							if ( event.target !== that.element[ 0 ] &&
+									event.target !== menuElement &&
+									!$.contains( menuElement, event.target ) ) {
+								that.close();
+							}
+						});
+					});
+				}
+			},
+			menufocus: function( event, ui ) {
+				var label, item;
+				// support: Firefox
+				// Prevent accidental activation of menu items in Firefox (#7024 #9118)
+				if ( this.isNewMenu ) {
+					this.isNewMenu = false;
+					if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
+						this.menu.blur();
+
+						this.document.one( "mousemove", function() {
+							$( event.target ).trigger( event.originalEvent );
+						});
+
+						return;
+					}
+				}
+
+				item = ui.item.data( "ui-autocomplete-item" );
+				if ( false !== this._trigger( "focus", event, { item: item } ) ) {
+					// use value to match what will end up in the input, if it was a key event
+					if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
+						this._value( item.value );
+					}
+				}
+
+				// Announce the value in the liveRegion
+				label = ui.item.attr( "aria-label" ) || item.value;
+				if ( label && $.trim( label ).length ) {
+					this.liveRegion.children().hide();
+					$( "<div>" ).text( label ).appendTo( this.liveRegion );
+				}
+			},
+			menuselect: function( event, ui ) {
+				var item = ui.item.data( "ui-autocomplete-item" ),
+					previous = this.previous;
+
+				// only trigger when focus was lost (click on menu)
+				if ( this.element[ 0 ] !== this.document[ 0 ].activeElement ) {
+					this.element.focus();
+					this.previous = previous;
+					// #6109 - IE triggers two focus events and the second
+					// is asynchronous, so we need to reset the previous
+					// term synchronously and asynchronously :-(
+					this._delay(function() {
+						this.previous = previous;
+						this.selectedItem = item;
+					});
+				}
+
+				if ( false !== this._trigger( "select", event, { item: item } ) ) {
+					this._value( item.value );
+				}
+				// reset the term after the select event
+				// this allows custom select handling to work properly
+				this.term = this._value();
+
+				this.close( event );
+				this.selectedItem = item;
+			}
+		});
+
+		this.liveRegion = $( "<span>", {
+				role: "status",
+				"aria-live": "assertive",
+				"aria-relevant": "additions"
+			})
+			.addClass( "ui-helper-hidden-accessible" )
+			.appendTo( this.document[ 0 ].body );
+
+		// turning off autocomplete prevents the browser from remembering the
+		// value when navigating through history, so we re-enable autocomplete
+		// if the page is unloaded before the widget is destroyed. #7790
+		this._on( this.window, {
+			beforeunload: function() {
+				this.element.removeAttr( "autocomplete" );
+			}
+		});
+	},
+
+	_destroy: function() {
+		clearTimeout( this.searching );
+		this.element
+			.removeClass( "ui-autocomplete-input" )
+			.removeAttr( "autocomplete" );
+		this.menu.element.remove();
+		this.liveRegion.remove();
+	},
+
+	_setOption: function( key, value ) {
+		this._super( key, value );
+		if ( key === "source" ) {
+			this._initSource();
+		}
+		if ( key === "appendTo" ) {
+			this.menu.element.appendTo( this._appendTo() );
+		}
+		if ( key === "disabled" && value && this.xhr ) {
+			this.xhr.abort();
+		}
+	},
+
+	_appendTo: function() {
+		var element = this.options.appendTo;
+
+		if ( element ) {
+			element = element.jquery || element.nodeType ?
+				$( element ) :
+				this.document.find( element ).eq( 0 );
+		}
+
+		if ( !element || !element[ 0 ] ) {
+			element = this.element.closest( ".ui-front" );
+		}
+
+		if ( !element.length ) {
+			element = this.document[ 0 ].body;
+		}
+
+		return element;
+	},
+
+	_initSource: function() {
+		var array, url,
+			that = this;
+		if ( $.isArray( this.options.source ) ) {
+			array = this.options.source;
+			this.source = function( request, response ) {
+				response( $.ui.autocomplete.filter( array, request.term ) );
+			};
+		} else if ( typeof this.options.source === "string" ) {
+			url = this.options.source;
+			this.source = function( request, response ) {
+				if ( that.xhr ) {
+					that.xhr.abort();
+				}
+				that.xhr = $.ajax({
+					url: url,
+					data: request,
+					dataType: "json",
+					success: function( data ) {
+						response( data );
+					},
+					error: function() {
+						response([]);
+					}
+				});
+			};
+		} else {
+			this.source = this.options.source;
+		}
+	},
+
+	_searchTimeout: function( event ) {
+		clearTimeout( this.searching );
+		this.searching = this._delay(function() {
+
+			// Search if the value has changed, or if the user retypes the same value (see #7434)
+			var equalValues = this.term === this._value(),
+				menuVisible = this.menu.element.is( ":visible" ),
+				modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
+
+			if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) {
+				this.selectedItem = null;
+				this.search( null, event );
+			}
+		}, this.options.delay );
+	},
+
+	search: function( value, event ) {
+		value = value != null ? value : this._value();
+
+		// always save the actual value, not the one passed as an argument
+		this.term = this._value();
+
+		if ( value.length < this.options.minLength ) {
+			return this.close( event );
+		}
+
+		if ( this._trigger( "search", event ) === false ) {
+			return;
+		}
+
+		return this._search( value );
+	},
+
+	_search: function( value ) {
+		this.pending++;
+		this.element.addClass( "ui-autocomplete-loading" );
+		this.cancelSearch = false;
+
+		this.source( { term: value }, this._response() );
+	},
+
+	_response: function() {
+		var index = ++this.requestIndex;
+
+		return $.proxy(function( content ) {
+			if ( index === this.requestIndex ) {
+				this.__response( content );
+			}
+
+			this.pending--;
+			if ( !this.pending ) {
+				this.element.removeClass( "ui-autocomplete-loading" );
+			}
+		}, this );
+	},
+
+	__response: function( content ) {
+		if ( content ) {
+			content = this._normalize( content );
+		}
+		this._trigger( "response", null, { content: content } );
+		if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
+			this._suggest( content );
+			this._trigger( "open" );
+		} else {
+			// use ._close() instead of .close() so we don't cancel future searches
+			this._close();
+		}
+	},
+
+	close: function( event ) {
+		this.cancelSearch = true;
+		this._close( event );
+	},
+
+	_close: function( event ) {
+		if ( this.menu.element.is( ":visible" ) ) {
+			this.menu.element.hide();
+			this.menu.blur();
+			this.isNewMenu = true;
+			this._trigger( "close", event );
+		}
+	},
+
+	_change: function( event ) {
+		if ( this.previous !== this._value() ) {
+			this._trigger( "change", event, { item: this.selectedItem } );
+		}
+	},
+
+	_normalize: function( items ) {
+		// assume all items have the right format when the first item is complete
+		if ( items.length && items[ 0 ].label && items[ 0 ].value ) {
+			return items;
+		}
+		return $.map( items, function( item ) {
+			if ( typeof item === "string" ) {
+				return {
+					label: item,
+					value: item
+				};
+			}
+			return $.extend( {}, item, {
+				label: item.label || item.value,
+				value: item.value || item.label
+			});
+		});
+	},
+
+	_suggest: function( items ) {
+		var ul = this.menu.element.empty();
+		this._renderMenu( ul, items );
+		this.isNewMenu = true;
+		this.menu.refresh();
+
+		// size and position menu
+		ul.show();
+		this._resizeMenu();
+		ul.position( $.extend({
+			of: this.element
+		}, this.options.position ) );
+
+		if ( this.options.autoFocus ) {
+			this.menu.next();
+		}
+	},
+
+	_resizeMenu: function() {
+		var ul = this.menu.element;
+		ul.outerWidth( Math.max(
+			// Firefox wraps long text (possibly a rounding bug)
+			// so we add 1px to avoid the wrapping (#7513)
+			ul.width( "" ).outerWidth() + 1,
+			this.element.outerWidth()
+		) );
+	},
+
+	_renderMenu: function( ul, items ) {
+		var that = this;
+		$.each( items, function( index, item ) {
+			that._renderItemData( ul, item );
+		});
+	},
+
+	_renderItemData: function( ul, item ) {
+		return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
+	},
+
+	_renderItem: function( ul, item ) {
+		return $( "<li>" ).text( item.label ).appendTo( ul );
+	},
+
+	_move: function( direction, event ) {
+		if ( !this.menu.element.is( ":visible" ) ) {
+			this.search( null, event );
+			return;
+		}
+		if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
+				this.menu.isLastItem() && /^next/.test( direction ) ) {
+
+			if ( !this.isMultiLine ) {
+				this._value( this.term );
+			}
+
+			this.menu.blur();
+			return;
+		}
+		this.menu[ direction ]( event );
+	},
+
+	widget: function() {
+		return this.menu.element;
+	},
+
+	_value: function() {
+		return this.valueMethod.apply( this.element, arguments );
+	},
+
+	_keyEvent: function( keyEvent, event ) {
+		if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
+			this._move( keyEvent, event );
+
+			// prevents moving cursor to beginning/end of the text field in some browsers
+			event.preventDefault();
+		}
+	}
+});
+
+$.extend( $.ui.autocomplete, {
+	escapeRegex: function( value ) {
+		return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
+	},
+	filter: function( array, term ) {
+		var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" );
+		return $.grep( array, function( value ) {
+			return matcher.test( value.label || value.value || value );
+		});
+	}
+});
+
+// live region extension, adding a `messages` option
+// NOTE: This is an experimental API. We are still investigating
+// a full solution for string manipulation and internationalization.
+$.widget( "ui.autocomplete", $.ui.autocomplete, {
+	options: {
+		messages: {
+			noResults: "No search results.",
+			results: function( amount ) {
+				return amount + ( amount > 1 ? " results are" : " result is" ) +
+					" available, use up and down arrow keys to navigate.";
+			}
+		}
+	},
+
+	__response: function( content ) {
+		var message;
+		this._superApply( arguments );
+		if ( this.options.disabled || this.cancelSearch ) {
+			return;
+		}
+		if ( content && content.length ) {
+			message = this.options.messages.results( content.length );
+		} else {
+			message = this.options.messages.noResults;
+		}
+		this.liveRegion.children().hide();
+		$( "<div>" ).text( message ).appendTo( this.liveRegion );
+	}
+});
+
+var autocomplete = $.ui.autocomplete;
+
+
+/*!
+ * jQuery UI Button 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/button/
+ */
+
+
+var lastActive,
+	baseClasses = "ui-button ui-widget ui-state-default ui-corner-all",
+	typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",
+	formResetHandler = function() {
+		var form = $( this );
+		setTimeout(function() {
+			form.find( ":ui-button" ).button( "refresh" );
+		}, 1 );
+	},
+	radioGroup = function( radio ) {
+		var name = radio.name,
+			form = radio.form,
+			radios = $( [] );
+		if ( name ) {
+			name = name.replace( /'/g, "\\'" );
+			if ( form ) {
+				radios = $( form ).find( "[name='" + name + "'][type=radio]" );
+			} else {
+				radios = $( "[name='" + name + "'][type=radio]", radio.ownerDocument )
+					.filter(function() {
+						return !this.form;
+					});
+			}
+		}
+		return radios;
+	};
+
+$.widget( "ui.button", {
+	version: "1.11.4",
+	defaultElement: "<button>",
+	options: {
+		disabled: null,
+		text: true,
+		label: null,
+		icons: {
+			primary: null,
+			secondary: null
+		}
+	},
+	_create: function() {
+		this.element.closest( "form" )
+			.unbind( "reset" + this.eventNamespace )
+			.bind( "reset" + this.eventNamespace, formResetHandler );
+
+		if ( typeof this.options.disabled !== "boolean" ) {
+			this.options.disabled = !!this.element.prop( "disabled" );
+		} else {
+			this.element.prop( "disabled", this.options.disabled );
+		}
+
+		this._determineButtonType();
+		this.hasTitle = !!this.buttonElement.attr( "title" );
+
+		var that = this,
+			options = this.options,
+			toggleButton = this.type === "checkbox" || this.type === "radio",
+			activeClass = !toggleButton ? "ui-state-active" : "";
+
+		if ( options.label === null ) {
+			options.label = (this.type === "input" ? this.buttonElement.val() : this.buttonElement.html());
+		}
+
+		this._hoverable( this.buttonElement );
+
+		this.buttonElement
+			.addClass( baseClasses )
+			.attr( "role", "button" )
+			.bind( "mouseenter" + this.eventNamespace, function() {
+				if ( options.disabled ) {
+					return;
+				}
+				if ( this === lastActive ) {
+					$( this ).addClass( "ui-state-active" );
+				}
+			})
+			.bind( "mouseleave" + this.eventNamespace, function() {
+				if ( options.disabled ) {
+					return;
+				}
+				$( this ).removeClass( activeClass );
+			})
+			.bind( "click" + this.eventNamespace, function( event ) {
+				if ( options.disabled ) {
+					event.preventDefault();
+					event.stopImmediatePropagation();
+				}
+			});
+
+		// Can't use _focusable() because the element that receives focus
+		// and the element that gets the ui-state-focus class are different
+		this._on({
+			focus: function() {
+				this.buttonElement.addClass( "ui-state-focus" );
+			},
+			blur: function() {
+				this.buttonElement.removeClass( "ui-state-focus" );
+			}
+		});
+
+		if ( toggleButton ) {
+			this.element.bind( "change" + this.eventNamespace, function() {
+				that.refresh();
+			});
+		}
+
+		if ( this.type === "checkbox" ) {
+			this.buttonElement.bind( "click" + this.eventNamespace, function() {
+				if ( options.disabled ) {
+					return false;
+				}
+			});
+		} else if ( this.type === "radio" ) {
+			this.buttonElement.bind( "click" + this.eventNamespace, function() {
+				if ( options.disabled ) {
+					return false;
+				}
+				$( this ).addClass( "ui-state-active" );
+				that.buttonElement.attr( "aria-pressed", "true" );
+
+				var radio = that.element[ 0 ];
+				radioGroup( radio )
+					.not( radio )
+					.map(function() {
+						return $( this ).button( "widget" )[ 0 ];
+					})
+					.removeClass( "ui-state-active" )
+					.attr( "aria-pressed", "false" );
+			});
+		} else {
+			this.buttonElement
+				.bind( "mousedown" + this.eventNamespace, function() {
+					if ( options.disabled ) {
+						return false;
+					}
+					$( this ).addClass( "ui-state-active" );
+					lastActive = this;
+					that.document.one( "mouseup", function() {
+						lastActive = null;
+					});
+				})
+				.bind( "mouseup" + this.eventNamespace, function() {
+					if ( options.disabled ) {
+						return false;
+					}
+					$( this ).removeClass( "ui-state-active" );
+				})
+				.bind( "keydown" + this.eventNamespace, function(event) {
+					if ( options.disabled ) {
+						return false;
+					}
+					if ( event.keyCode === $.ui.keyCode.SPACE || event.keyCode === $.ui.keyCode.ENTER ) {
+						$( this ).addClass( "ui-state-active" );
+					}
+				})
+				// see #8559, we bind to blur here in case the button element loses
+				// focus between keydown and keyup, it would be left in an "active" state
+				.bind( "keyup" + this.eventNamespace + " blur" + this.eventNamespace, function() {
+					$( this ).removeClass( "ui-state-active" );
+				});
+
+			if ( this.buttonElement.is("a") ) {
+				this.buttonElement.keyup(function(event) {
+					if ( event.keyCode === $.ui.keyCode.SPACE ) {
+						// TODO pass through original event correctly (just as 2nd argument doesn't work)
+						$( this ).click();
+					}
+				});
+			}
+		}
+
+		this._setOption( "disabled", options.disabled );
+		this._resetButton();
+	},
+
+	_determineButtonType: function() {
+		var ancestor, labelSelector, checked;
+
+		if ( this.element.is("[type=checkbox]") ) {
+			this.type = "checkbox";
+		} else if ( this.element.is("[type=radio]") ) {
+			this.type = "radio";
+		} else if ( this.element.is("input") ) {
+			this.type = "input";
+		} else {
+			this.type = "button";
+		}
+
+		if ( this.type === "checkbox" || this.type === "radio" ) {
+			// we don't search against the document in case the element
+			// is disconnected from the DOM
+			ancestor = this.element.parents().last();
+			labelSelector = "label[for='" + this.element.attr("id") + "']";
+			this.buttonElement = ancestor.find( labelSelector );
+			if ( !this.buttonElement.length ) {
+				ancestor = ancestor.length ? ancestor.siblings() : this.element.siblings();
+				this.buttonElement = ancestor.filter( labelSelector );
+				if ( !this.buttonElement.length ) {
+					this.buttonElement = ancestor.find( labelSelector );
+				}
+			}
+			this.element.addClass( "ui-helper-hidden-accessible" );
+
+			checked = this.element.is( ":checked" );
+			if ( checked ) {
+				this.buttonElement.addClass( "ui-state-active" );
+			}
+			this.buttonElement.prop( "aria-pressed", checked );
+		} else {
+			this.buttonElement = this.element;
+		}
+	},
+
+	widget: function() {
+		return this.buttonElement;
+	},
+
+	_destroy: function() {
+		this.element
+			.removeClass( "ui-helper-hidden-accessible" );
+		this.buttonElement
+			.removeClass( baseClasses + " ui-state-active " + typeClasses )
+			.removeAttr( "role" )
+			.removeAttr( "aria-pressed" )
+			.html( this.buttonElement.find(".ui-button-text").html() );
+
+		if ( !this.hasTitle ) {
+			this.buttonElement.removeAttr( "title" );
+		}
+	},
+
+	_setOption: function( key, value ) {
+		this._super( key, value );
+		if ( key === "disabled" ) {
+			this.widget().toggleClass( "ui-state-disabled", !!value );
+			this.element.prop( "disabled", !!value );
+			if ( value ) {
+				if ( this.type === "checkbox" || this.type === "radio" ) {
+					this.buttonElement.removeClass( "ui-state-focus" );
+				} else {
+					this.buttonElement.removeClass( "ui-state-focus ui-state-active" );
+				}
+			}
+			return;
+		}
+		this._resetButton();
+	},
+
+	refresh: function() {
+		//See #8237 & #8828
+		var isDisabled = this.element.is( "input, button" ) ? this.element.is( ":disabled" ) : this.element.hasClass( "ui-button-disabled" );
+
+		if ( isDisabled !== this.options.disabled ) {
+			this._setOption( "disabled", isDisabled );
+		}
+		if ( this.type === "radio" ) {
+			radioGroup( this.element[0] ).each(function() {
+				if ( $( this ).is( ":checked" ) ) {
+					$( this ).button( "widget" )
+						.addClass( "ui-state-active" )
+						.attr( "aria-pressed", "true" );
+				} else {
+					$( this ).button( "widget" )
+						.removeClass( "ui-state-active" )
+						.attr( "aria-pressed", "false" );
+				}
+			});
+		} else if ( this.type === "checkbox" ) {
+			if ( this.element.is( ":checked" ) ) {
+				this.buttonElement
+					.addClass( "ui-state-active" )
+					.attr( "aria-pressed", "true" );
+			} else {
+				this.buttonElement
+					.removeClass( "ui-state-active" )
+					.attr( "aria-pressed", "false" );
+			}
+		}
+	},
+
+	_resetButton: function() {
+		if ( this.type === "input" ) {
+			if ( this.options.label ) {
+				this.element.val( this.options.label );
+			}
+			return;
+		}
+		var buttonElement = this.buttonElement.removeClass( typeClasses ),
+			buttonText = $( "<span></span>", this.document[0] )
+				.addClass( "ui-button-text" )
+				.html( this.options.label )
+				.appendTo( buttonElement.empty() )
+				.text(),
+			icons = this.options.icons,
+			multipleIcons = icons.primary && icons.secondary,
+			buttonClasses = [];
+
+		if ( icons.primary || icons.secondary ) {
+			if ( this.options.text ) {
+				buttonClasses.push( "ui-button-text-icon" + ( multipleIcons ? "s" : ( icons.primary ? "-primary" : "-secondary" ) ) );
+			}
+
+			if ( icons.primary ) {
+				buttonElement.prepend( "<span class='ui-button-icon-primary ui-icon " + icons.primary + "'></span>" );
+			}
+
+			if ( icons.secondary ) {
+				buttonElement.append( "<span class='ui-button-icon-secondary ui-icon " + icons.secondary + "'></span>" );
+			}
+
+			if ( !this.options.text ) {
+				buttonClasses.push( multipleIcons ? "ui-button-icons-only" : "ui-button-icon-only" );
+
+				if ( !this.hasTitle ) {
+					buttonElement.attr( "title", $.trim( buttonText ) );
+				}
+			}
+		} else {
+			buttonClasses.push( "ui-button-text-only" );
+		}
+		buttonElement.addClass( buttonClasses.join( " " ) );
+	}
+});
+
+$.widget( "ui.buttonset", {
+	version: "1.11.4",
+	options: {
+		items: "button, input[type=button], input[type=submit], input[type=reset], input[type=checkbox], input[type=radio], a, :data(ui-button)"
+	},
+
+	_create: function() {
+		this.element.addClass( "ui-buttonset" );
+	},
+
+	_init: function() {
+		this.refresh();
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "disabled" ) {
+			this.buttons.button( "option", key, value );
+		}
+
+		this._super( key, value );
+	},
+
+	refresh: function() {
+		var rtl = this.element.css( "direction" ) === "rtl",
+			allButtons = this.element.find( this.options.items ),
+			existingButtons = allButtons.filter( ":ui-button" );
+
+		// Initialize new buttons
+		allButtons.not( ":ui-button" ).button();
+
+		// Refresh existing buttons
+		existingButtons.button( "refresh" );
+
+		this.buttons = allButtons
+			.map(function() {
+				return $( this ).button( "widget" )[ 0 ];
+			})
+				.removeClass( "ui-corner-all ui-corner-left ui-corner-right" )
+				.filter( ":first" )
+					.addClass( rtl ? "ui-corner-right" : "ui-corner-left" )
+				.end()
+				.filter( ":last" )
+					.addClass( rtl ? "ui-corner-left" : "ui-corner-right" )
+				.end()
+			.end();
+	},
+
+	_destroy: function() {
+		this.element.removeClass( "ui-buttonset" );
+		this.buttons
+			.map(function() {
+				return $( this ).button( "widget" )[ 0 ];
+			})
+				.removeClass( "ui-corner-left ui-corner-right" )
+			.end()
+			.button( "destroy" );
+	}
+});
+
+var button = $.ui.button;
+
+
+/*!
+ * jQuery UI Datepicker 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/datepicker/
+ */
+
+
+$.extend($.ui, { datepicker: { version: "1.11.4" } });
+
+var datepicker_instActive;
+
+function datepicker_getZindex( elem ) {
+	var position, value;
+	while ( elem.length && elem[ 0 ] !== document ) {
+		// Ignore z-index if position is set to a value where z-index is ignored by the browser
+		// This makes behavior of this function consistent across browsers
+		// WebKit always returns auto if the element is positioned
+		position = elem.css( "position" );
+		if ( position === "absolute" || position === "relative" || position === "fixed" ) {
+			// IE returns 0 when zIndex is not specified
+			// other browsers return a string
+			// we ignore the case of nested elements with an explicit value of 0
+			// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
+			value = parseInt( elem.css( "zIndex" ), 10 );
+			if ( !isNaN( value ) && value !== 0 ) {
+				return value;
+			}
+		}
+		elem = elem.parent();
+	}
+
+	return 0;
+}
+/* Date picker manager.
+   Use the singleton instance of this class, $.datepicker, to interact with the date picker.
+   Settings for (groups of) date pickers are maintained in an instance object,
+   allowing multiple different settings on the same page. */
+
+function Datepicker() {
+	this._curInst = null; // The current instance in use
+	this._keyEvent = false; // If the last event was a key event
+	this._disabledInputs = []; // List of date picker inputs that have been disabled
+	this._datepickerShowing = false; // True if the popup picker is showing , false if not
+	this._inDialog = false; // True if showing within a "dialog", false if not
+	this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division
+	this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class
+	this._appendClass = "ui-datepicker-append"; // The name of the append marker class
+	this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class
+	this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class
+	this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class
+	this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class
+	this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class
+	this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class
+	this.regional = []; // Available regional settings, indexed by language code
+	this.regional[""] = { // Default regional settings
+		closeText: "Done", // Display text for close link
+		prevText: "Prev", // Display text for previous month link
+		nextText: "Next", // Display text for next month link
+		currentText: "Today", // Display text for current month link
+		monthNames: ["January","February","March","April","May","June",
+			"July","August","September","October","November","December"], // Names of months for drop-down and formatting
+		monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], // For formatting
+		dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], // For formatting
+		dayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], // For formatting
+		dayNamesMin: ["Su","Mo","Tu","We","Th","Fr","Sa"], // Column headings for days starting at Sunday
+		weekHeader: "Wk", // Column header for week of the year
+		dateFormat: "mm/dd/yy", // See format options on parseDate
+		firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
+		isRTL: false, // True if right-to-left language, false if left-to-right
+		showMonthAfterYear: false, // True if the year select precedes month, false for month then year
+		yearSuffix: "" // Additional text to append to the year in the month headers
+	};
+	this._defaults = { // Global defaults for all the date picker instances
+		showOn: "focus", // "focus" for popup on focus,
+			// "button" for trigger button, or "both" for either
+		showAnim: "fadeIn", // Name of jQuery animation for popup
+		showOptions: {}, // Options for enhanced animations
+		defaultDate: null, // Used when field is blank: actual date,
+			// +/-number for offset from today, null for today
+		appendText: "", // Display text following the input box, e.g. showing the format
+		buttonText: "...", // Text for trigger button
+		buttonImage: "", // URL for trigger button image
+		buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
+		hideIfNoPrevNext: false, // True to hide next/previous month links
+			// if not applicable, false to just disable them
+		navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
+		gotoCurrent: false, // True if today link goes back to current selection instead
+		changeMonth: false, // True if month can be selected directly, false if only prev/next
+		changeYear: false, // True if year can be selected directly, false if only prev/next
+		yearRange: "c-10:c+10", // Range of years to display in drop-down,
+			// either relative to today's year (-nn:+nn), relative to currently displayed year
+			// (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)
+		showOtherMonths: false, // True to show dates in other months, false to leave blank
+		selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable
+		showWeek: false, // True to show week of the year, false to not show it
+		calculateWeek: this.iso8601Week, // How to calculate the week of the year,
+			// takes a Date and returns the number of the week for it
+		shortYearCutoff: "+10", // Short year values < this are in the current century,
+			// > this are in the previous century,
+			// string value starting with "+" for current year + value
+		minDate: null, // The earliest selectable date, or null for no limit
+		maxDate: null, // The latest selectable date, or null for no limit
+		duration: "fast", // Duration of display/closure
+		beforeShowDay: null, // Function that takes a date and returns an array with
+			// [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "",
+			// [2] = cell title (optional), e.g. $.datepicker.noWeekends
+		beforeShow: null, // Function that takes an input field and
+			// returns a set of custom settings for the date picker
+		onSelect: null, // Define a callback function when a date is selected
+		onChangeMonthYear: null, // Define a callback function when the month or year is changed
+		onClose: null, // Define a callback function when the datepicker is closed
+		numberOfMonths: 1, // Number of months to show at a time
+		showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)
+		stepMonths: 1, // Number of months to step back/forward
+		stepBigMonths: 12, // Number of months to step back/forward for the big links
+		altField: "", // Selector for an alternate field to store selected dates into
+		altFormat: "", // The date format to use for the alternate field
+		constrainInput: true, // The input is constrained by the current date format
+		showButtonPanel: false, // True to show button panel, false to not show it
+		autoSize: false, // True to size the input for the date format, false to leave as is
+		disabled: false // The initial disabled state
+	};
+	$.extend(this._defaults, this.regional[""]);
+	this.regional.en = $.extend( true, {}, this.regional[ "" ]);
+	this.regional[ "en-US" ] = $.extend( true, {}, this.regional.en );
+	this.dpDiv = datepicker_bindHover($("<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>"));
+}
+
+$.extend(Datepicker.prototype, {
+	/* Class name added to elements to indicate already configured with a date picker. */
+	markerClassName: "hasDatepicker",
+
+	//Keep track of the maximum number of rows displayed (see #7043)
+	maxRows: 4,
+
+	// TODO rename to "widget" when switching to widget factory
+	_widgetDatepicker: function() {
+		return this.dpDiv;
+	},
+
+	/* Override the default settings for all instances of the date picker.
+	 * @param  settings  object - the new settings to use as defaults (anonymous object)
+	 * @return the manager object
+	 */
+	setDefaults: function(settings) {
+		datepicker_extendRemove(this._defaults, settings || {});
+		return this;
+	},
+
+	/* Attach the date picker to a jQuery selection.
+	 * @param  target	element - the target input field or division or span
+	 * @param  settings  object - the new settings to use for this date picker instance (anonymous)
+	 */
+	_attachDatepicker: function(target, settings) {
+		var nodeName, inline, inst;
+		nodeName = target.nodeName.toLowerCase();
+		inline = (nodeName === "div" || nodeName === "span");
+		if (!target.id) {
+			this.uuid += 1;
+			target.id = "dp" + this.uuid;
+		}
+		inst = this._newInst($(target), inline);
+		inst.settings = $.extend({}, settings || {});
+		if (nodeName === "input") {
+			this._connectDatepicker(target, inst);
+		} else if (inline) {
+			this._inlineDatepicker(target, inst);
+		}
+	},
+
+	/* Create a new instance object. */
+	_newInst: function(target, inline) {
+		var id = target[0].id.replace(/([^A-Za-z0-9_\-])/g, "\\\\$1"); // escape jQuery meta chars
+		return {id: id, input: target, // associated target
+			selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
+			drawMonth: 0, drawYear: 0, // month being drawn
+			inline: inline, // is datepicker inline or not
+			dpDiv: (!inline ? this.dpDiv : // presentation div
+			datepicker_bindHover($("<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")))};
+	},
+
+	/* Attach the date picker to an input field. */
+	_connectDatepicker: function(target, inst) {
+		var input = $(target);
+		inst.append = $([]);
+		inst.trigger = $([]);
+		if (input.hasClass(this.markerClassName)) {
+			return;
+		}
+		this._attachments(input, inst);
+		input.addClass(this.markerClassName).keydown(this._doKeyDown).
+			keypress(this._doKeyPress).keyup(this._doKeyUp);
+		this._autoSize(inst);
+		$.data(target, "datepicker", inst);
+		//If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)
+		if( inst.settings.disabled ) {
+			this._disableDatepicker( target );
+		}
+	},
+
+	/* Make attachments based on settings. */
+	_attachments: function(input, inst) {
+		var showOn, buttonText, buttonImage,
+			appendText = this._get(inst, "appendText"),
+			isRTL = this._get(inst, "isRTL");
+
+		if (inst.append) {
+			inst.append.remove();
+		}
+		if (appendText) {
+			inst.append = $("<span class='" + this._appendClass + "'>" + appendText + "</span>");
+			input[isRTL ? "before" : "after"](inst.append);
+		}
+
+		input.unbind("focus", this._showDatepicker);
+
+		if (inst.trigger) {
+			inst.trigger.remove();
+		}
+
+		showOn = this._get(inst, "showOn");
+		if (showOn === "focus" || showOn === "both") { // pop-up date picker when in the marked field
+			input.focus(this._showDatepicker);
+		}
+		if (showOn === "button" || showOn === "both") { // pop-up date picker when button clicked
+			buttonText = this._get(inst, "buttonText");
+			buttonImage = this._get(inst, "buttonImage");
+			inst.trigger = $(this._get(inst, "buttonImageOnly") ?
+				$("<img/>").addClass(this._triggerClass).
+					attr({ src: buttonImage, alt: buttonText, title: buttonText }) :
+				$("<button type='button'></button>").addClass(this._triggerClass).
+					html(!buttonImage ? buttonText : $("<img/>").attr(
+					{ src:buttonImage, alt:buttonText, title:buttonText })));
+			input[isRTL ? "before" : "after"](inst.trigger);
+			inst.trigger.click(function() {
+				if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) {
+					$.datepicker._hideDatepicker();
+				} else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) {
+					$.datepicker._hideDatepicker();
+					$.datepicker._showDatepicker(input[0]);
+				} else {
+					$.datepicker._showDatepicker(input[0]);
+				}
+				return false;
+			});
+		}
+	},
+
+	/* Apply the maximum length for the date format. */
+	_autoSize: function(inst) {
+		if (this._get(inst, "autoSize") && !inst.inline) {
+			var findMax, max, maxI, i,
+				date = new Date(2009, 12 - 1, 20), // Ensure double digits
+				dateFormat = this._get(inst, "dateFormat");
+
+			if (dateFormat.match(/[DM]/)) {
+				findMax = function(names) {
+					max = 0;
+					maxI = 0;
+					for (i = 0; i < names.length; i++) {
+						if (names[i].length > max) {
+							max = names[i].length;
+							maxI = i;
+						}
+					}
+					return maxI;
+				};
+				date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ?
+					"monthNames" : "monthNamesShort"))));
+				date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ?
+					"dayNames" : "dayNamesShort"))) + 20 - date.getDay());
+			}
+			inst.input.attr("size", this._formatDate(inst, date).length);
+		}
+	},
+
+	/* Attach an inline date picker to a div. */
+	_inlineDatepicker: function(target, inst) {
+		var divSpan = $(target);
+		if (divSpan.hasClass(this.markerClassName)) {
+			return;
+		}
+		divSpan.addClass(this.markerClassName).append(inst.dpDiv);
+		$.data(target, "datepicker", inst);
+		this._setDate(inst, this._getDefaultDate(inst), true);
+		this._updateDatepicker(inst);
+		this._updateAlternate(inst);
+		//If disabled option is true, disable the datepicker before showing it (see ticket #5665)
+		if( inst.settings.disabled ) {
+			this._disableDatepicker( target );
+		}
+		// Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements
+		// http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height
+		inst.dpDiv.css( "display", "block" );
+	},
+
+	/* Pop-up the date picker in a "dialog" box.
+	 * @param  input element - ignored
+	 * @param  date	string or Date - the initial date to display
+	 * @param  onSelect  function - the function to call when a date is selected
+	 * @param  settings  object - update the dialog date picker instance's settings (anonymous object)
+	 * @param  pos int[2] - coordinates for the dialog's position within the screen or
+	 *					event - with x/y coordinates or
+	 *					leave empty for default (screen centre)
+	 * @return the manager object
+	 */
+	_dialogDatepicker: function(input, date, onSelect, settings, pos) {
+		var id, browserWidth, browserHeight, scrollX, scrollY,
+			inst = this._dialogInst; // internal instance
+
+		if (!inst) {
+			this.uuid += 1;
+			id = "dp" + this.uuid;
+			this._dialogInput = $("<input type='text' id='" + id +
+				"' style='position: absolute; top: -100px; width: 0px;'/>");
+			this._dialogInput.keydown(this._doKeyDown);
+			$("body").append(this._dialogInput);
+			inst = this._dialogInst = this._newInst(this._dialogInput, false);
+			inst.settings = {};
+			$.data(this._dialogInput[0], "datepicker", inst);
+		}
+		datepicker_extendRemove(inst.settings, settings || {});
+		date = (date && date.constructor === Date ? this._formatDate(inst, date) : date);
+		this._dialogInput.val(date);
+
+		this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null);
+		if (!this._pos) {
+			browserWidth = document.documentElement.clientWidth;
+			browserHeight = document.documentElement.clientHeight;
+			scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
+			scrollY = document.documentElement.scrollTop || document.body.scrollTop;
+			this._pos = // should use actual width/height below
+				[(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY];
+		}
+
+		// move input on screen for focus, but hidden behind dialog
+		this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px");
+		inst.settings.onSelect = onSelect;
+		this._inDialog = true;
+		this.dpDiv.addClass(this._dialogClass);
+		this._showDatepicker(this._dialogInput[0]);
+		if ($.blockUI) {
+			$.blockUI(this.dpDiv);
+		}
+		$.data(this._dialogInput[0], "datepicker", inst);
+		return this;
+	},
+
+	/* Detach a datepicker from its control.
+	 * @param  target	element - the target input field or division or span
+	 */
+	_destroyDatepicker: function(target) {
+		var nodeName,
+			$target = $(target),
+			inst = $.data(target, "datepicker");
+
+		if (!$target.hasClass(this.markerClassName)) {
+			return;
+		}
+
+		nodeName = target.nodeName.toLowerCase();
+		$.removeData(target, "datepicker");
+		if (nodeName === "input") {
+			inst.append.remove();
+			inst.trigger.remove();
+			$target.removeClass(this.markerClassName).
+				unbind("focus", this._showDatepicker).
+				unbind("keydown", this._doKeyDown).
+				unbind("keypress", this._doKeyPress).
+				unbind("keyup", this._doKeyUp);
+		} else if (nodeName === "div" || nodeName === "span") {
+			$target.removeClass(this.markerClassName).empty();
+		}
+
+		if ( datepicker_instActive === inst ) {
+			datepicker_instActive = null;
+		}
+	},
+
+	/* Enable the date picker to a jQuery selection.
+	 * @param  target	element - the target input field or division or span
+	 */
+	_enableDatepicker: function(target) {
+		var nodeName, inline,
+			$target = $(target),
+			inst = $.data(target, "datepicker");
+
+		if (!$target.hasClass(this.markerClassName)) {
+			return;
+		}
+
+		nodeName = target.nodeName.toLowerCase();
+		if (nodeName === "input") {
+			target.disabled = false;
+			inst.trigger.filter("button").
+				each(function() { this.disabled = false; }).end().
+				filter("img").css({opacity: "1.0", cursor: ""});
+		} else if (nodeName === "div" || nodeName === "span") {
+			inline = $target.children("." + this._inlineClass);
+			inline.children().removeClass("ui-state-disabled");
+			inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
+				prop("disabled", false);
+		}
+		this._disabledInputs = $.map(this._disabledInputs,
+			function(value) { return (value === target ? null : value); }); // delete entry
+	},
+
+	/* Disable the date picker to a jQuery selection.
+	 * @param  target	element - the target input field or division or span
+	 */
+	_disableDatepicker: function(target) {
+		var nodeName, inline,
+			$target = $(target),
+			inst = $.data(target, "datepicker");
+
+		if (!$target.hasClass(this.markerClassName)) {
+			return;
+		}
+
+		nodeName = target.nodeName.toLowerCase();
+		if (nodeName === "input") {
+			target.disabled = true;
+			inst.trigger.filter("button").
+				each(function() { this.disabled = true; }).end().
+				filter("img").css({opacity: "0.5", cursor: "default"});
+		} else if (nodeName === "div" || nodeName === "span") {
+			inline = $target.children("." + this._inlineClass);
+			inline.children().addClass("ui-state-disabled");
+			inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
+				prop("disabled", true);
+		}
+		this._disabledInputs = $.map(this._disabledInputs,
+			function(value) { return (value === target ? null : value); }); // delete entry
+		this._disabledInputs[this._disabledInputs.length] = target;
+	},
+
+	/* Is the first field in a jQuery collection disabled as a datepicker?
+	 * @param  target	element - the target input field or division or span
+	 * @return boolean - true if disabled, false if enabled
+	 */
+	_isDisabledDatepicker: function(target) {
+		if (!target) {
+			return false;
+		}
+		for (var i = 0; i < this._disabledInputs.length; i++) {
+			if (this._disabledInputs[i] === target) {
+				return true;
+			}
+		}
+		return false;
+	},
+
+	/* Retrieve the instance data for the target control.
+	 * @param  target  element - the target input field or division or span
+	 * @return  object - the associated instance data
+	 * @throws  error if a jQuery problem getting data
+	 */
+	_getInst: function(target) {
+		try {
+			return $.data(target, "datepicker");
+		}
+		catch (err) {
+			throw "Missing instance data for this datepicker";
+		}
+	},
+
+	/* Update or retrieve the settings for a date picker attached to an input field or division.
+	 * @param  target  element - the target input field or division or span
+	 * @param  name	object - the new settings to update or
+	 *				string - the name of the setting to change or retrieve,
+	 *				when retrieving also "all" for all instance settings or
+	 *				"defaults" for all global defaults
+	 * @param  value   any - the new value for the setting
+	 *				(omit if above is an object or to retrieve a value)
+	 */
+	_optionDatepicker: function(target, name, value) {
+		var settings, date, minDate, maxDate,
+			inst = this._getInst(target);
+
+		if (arguments.length === 2 && typeof name === "string") {
+			return (name === "defaults" ? $.extend({}, $.datepicker._defaults) :
+				(inst ? (name === "all" ? $.extend({}, inst.settings) :
+				this._get(inst, name)) : null));
+		}
+
+		settings = name || {};
+		if (typeof name === "string") {
+			settings = {};
+			settings[name] = value;
+		}
+
+		if (inst) {
+			if (this._curInst === inst) {
+				this._hideDatepicker();
+			}
+
+			date = this._getDateDatepicker(target, true);
+			minDate = this._getMinMaxDate(inst, "min");
+			maxDate = this._getMinMaxDate(inst, "max");
+			datepicker_extendRemove(inst.settings, settings);
+			// reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided
+			if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) {
+				inst.settings.minDate = this._formatDate(inst, minDate);
+			}
+			if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) {
+				inst.settings.maxDate = this._formatDate(inst, maxDate);
+			}
+			if ( "disabled" in settings ) {
+				if ( settings.disabled ) {
+					this._disableDatepicker(target);
+				} else {
+					this._enableDatepicker(target);
+				}
+			}
+			this._attachments($(target), inst);
+			this._autoSize(inst);
+			this._setDate(inst, date);
+			this._updateAlternate(inst);
+			this._updateDatepicker(inst);
+		}
+	},
+
+	// change method deprecated
+	_changeDatepicker: function(target, name, value) {
+		this._optionDatepicker(target, name, value);
+	},
+
+	/* Redraw the date picker attached to an input field or division.
+	 * @param  target  element - the target input field or division or span
+	 */
+	_refreshDatepicker: function(target) {
+		var inst = this._getInst(target);
+		if (inst) {
+			this._updateDatepicker(inst);
+		}
+	},
+
+	/* Set the dates for a jQuery selection.
+	 * @param  target element - the target input field or division or span
+	 * @param  date	Date - the new date
+	 */
+	_setDateDatepicker: function(target, date) {
+		var inst = this._getInst(target);
+		if (inst) {
+			this._setDate(inst, date);
+			this._updateDatepicker(inst);
+			this._updateAlternate(inst);
+		}
+	},
+
+	/* Get the date(s) for the first entry in a jQuery selection.
+	 * @param  target element - the target input field or division or span
+	 * @param  noDefault boolean - true if no default date is to be used
+	 * @return Date - the current date
+	 */
+	_getDateDatepicker: function(target, noDefault) {
+		var inst = this._getInst(target);
+		if (inst && !inst.inline) {
+			this._setDateFromField(inst, noDefault);
+		}
+		return (inst ? this._getDate(inst) : null);
+	},
+
+	/* Handle keystrokes. */
+	_doKeyDown: function(event) {
+		var onSelect, dateStr, sel,
+			inst = $.datepicker._getInst(event.target),
+			handled = true,
+			isRTL = inst.dpDiv.is(".ui-datepicker-rtl");
+
+		inst._keyEvent = true;
+		if ($.datepicker._datepickerShowing) {
+			switch (event.keyCode) {
+				case 9: $.datepicker._hideDatepicker();
+						handled = false;
+						break; // hide on tab out
+				case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." +
+									$.datepicker._currentClass + ")", inst.dpDiv);
+						if (sel[0]) {
+							$.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]);
+						}
+
+						onSelect = $.datepicker._get(inst, "onSelect");
+						if (onSelect) {
+							dateStr = $.datepicker._formatDate(inst);
+
+							// trigger custom callback
+							onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);
+						} else {
+							$.datepicker._hideDatepicker();
+						}
+
+						return false; // don't submit the form
+				case 27: $.datepicker._hideDatepicker();
+						break; // hide on escape
+				case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+							-$.datepicker._get(inst, "stepBigMonths") :
+							-$.datepicker._get(inst, "stepMonths")), "M");
+						break; // previous month/year on page up/+ ctrl
+				case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+							+$.datepicker._get(inst, "stepBigMonths") :
+							+$.datepicker._get(inst, "stepMonths")), "M");
+						break; // next month/year on page down/+ ctrl
+				case 35: if (event.ctrlKey || event.metaKey) {
+							$.datepicker._clearDate(event.target);
+						}
+						handled = event.ctrlKey || event.metaKey;
+						break; // clear on ctrl or command +end
+				case 36: if (event.ctrlKey || event.metaKey) {
+							$.datepicker._gotoToday(event.target);
+						}
+						handled = event.ctrlKey || event.metaKey;
+						break; // current on ctrl or command +home
+				case 37: if (event.ctrlKey || event.metaKey) {
+							$.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D");
+						}
+						handled = event.ctrlKey || event.metaKey;
+						// -1 day on ctrl or command +left
+						if (event.originalEvent.altKey) {
+							$.datepicker._adjustDate(event.target, (event.ctrlKey ?
+								-$.datepicker._get(inst, "stepBigMonths") :
+								-$.datepicker._get(inst, "stepMonths")), "M");
+						}
+						// next month/year on alt +left on Mac
+						break;
+				case 38: if (event.ctrlKey || event.metaKey) {
+							$.datepicker._adjustDate(event.target, -7, "D");
+						}
+						handled = event.ctrlKey || event.metaKey;
+						break; // -1 week on ctrl or command +up
+				case 39: if (event.ctrlKey || event.metaKey) {
+							$.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D");
+						}
+						handled = event.ctrlKey || event.metaKey;
+						// +1 day on ctrl or command +right
+						if (event.originalEvent.altKey) {
+							$.datepicker._adjustDate(event.target, (event.ctrlKey ?
+								+$.datepicker._get(inst, "stepBigMonths") :
+								+$.datepicker._get(inst, "stepMonths")), "M");
+						}
+						// next month/year on alt +right
+						break;
+				case 40: if (event.ctrlKey || event.metaKey) {
+							$.datepicker._adjustDate(event.target, +7, "D");
+						}
+						handled = event.ctrlKey || event.metaKey;
+						break; // +1 week on ctrl or command +down
+				default: handled = false;
+			}
+		} else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home
+			$.datepicker._showDatepicker(this);
+		} else {
+			handled = false;
+		}
+
+		if (handled) {
+			event.preventDefault();
+			event.stopPropagation();
+		}
+	},
+
+	/* Filter entered characters - based on date format. */
+	_doKeyPress: function(event) {
+		var chars, chr,
+			inst = $.datepicker._getInst(event.target);
+
+		if ($.datepicker._get(inst, "constrainInput")) {
+			chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat"));
+			chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode);
+			return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1);
+		}
+	},
+
+	/* Synchronise manual entry and field/alternate field. */
+	_doKeyUp: function(event) {
+		var date,
+			inst = $.datepicker._getInst(event.target);
+
+		if (inst.input.val() !== inst.lastVal) {
+			try {
+				date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"),
+					(inst.input ? inst.input.val() : null),
+					$.datepicker._getFormatConfig(inst));
+
+				if (date) { // only if valid
+					$.datepicker._setDateFromField(inst);
+					$.datepicker._updateAlternate(inst);
+					$.datepicker._updateDatepicker(inst);
+				}
+			}
+			catch (err) {
+			}
+		}
+		return true;
+	},
+
+	/* Pop-up the date picker for a given input field.
+	 * If false returned from beforeShow event handler do not show.
+	 * @param  input  element - the input field attached to the date picker or
+	 *					event - if triggered by focus
+	 */
+	_showDatepicker: function(input) {
+		input = input.target || input;
+		if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger
+			input = $("input", input.parentNode)[0];
+		}
+
+		if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here
+			return;
+		}
+
+		var inst, beforeShow, beforeShowSettings, isFixed,
+			offset, showAnim, duration;
+
+		inst = $.datepicker._getInst(input);
+		if ($.datepicker._curInst && $.datepicker._curInst !== inst) {
+			$.datepicker._curInst.dpDiv.stop(true, true);
+			if ( inst && $.datepicker._datepickerShowing ) {
+				$.datepicker._hideDatepicker( $.datepicker._curInst.input[0] );
+			}
+		}
+
+		beforeShow = $.datepicker._get(inst, "beforeShow");
+		beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {};
+		if(beforeShowSettings === false){
+			return;
+		}
+		datepicker_extendRemove(inst.settings, beforeShowSettings);
+
+		inst.lastVal = null;
+		$.datepicker._lastInput = input;
+		$.datepicker._setDateFromField(inst);
+
+		if ($.datepicker._inDialog) { // hide cursor
+			input.value = "";
+		}
+		if (!$.datepicker._pos) { // position below input
+			$.datepicker._pos = $.datepicker._findPos(input);
+			$.datepicker._pos[1] += input.offsetHeight; // add the height
+		}
+
+		isFixed = false;
+		$(input).parents().each(function() {
+			isFixed |= $(this).css("position") === "fixed";
+			return !isFixed;
+		});
+
+		offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]};
+		$.datepicker._pos = null;
+		//to avoid flashes on Firefox
+		inst.dpDiv.empty();
+		// determine sizing offscreen
+		inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"});
+		$.datepicker._updateDatepicker(inst);
+		// fix width for dynamic number of date pickers
+		// and adjust position before showing
+		offset = $.datepicker._checkOffset(inst, offset, isFixed);
+		inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ?
+			"static" : (isFixed ? "fixed" : "absolute")), display: "none",
+			left: offset.left + "px", top: offset.top + "px"});
+
+		if (!inst.inline) {
+			showAnim = $.datepicker._get(inst, "showAnim");
+			duration = $.datepicker._get(inst, "duration");
+			inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 );
+			$.datepicker._datepickerShowing = true;
+
+			if ( $.effects && $.effects.effect[ showAnim ] ) {
+				inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration);
+			} else {
+				inst.dpDiv[showAnim || "show"](showAnim ? duration : null);
+			}
+
+			if ( $.datepicker._shouldFocusInput( inst ) ) {
+				inst.input.focus();
+			}
+
+			$.datepicker._curInst = inst;
+		}
+	},
+
+	/* Generate the date picker content. */
+	_updateDatepicker: function(inst) {
+		this.maxRows = 4; //Reset the max number of rows being displayed (see #7043)
+		datepicker_instActive = inst; // for delegate hover events
+		inst.dpDiv.empty().append(this._generateHTML(inst));
+		this._attachHandlers(inst);
+
+		var origyearshtml,
+			numMonths = this._getNumberOfMonths(inst),
+			cols = numMonths[1],
+			width = 17,
+			activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" );
+
+		if ( activeCell.length > 0 ) {
+			datepicker_handleMouseover.apply( activeCell.get( 0 ) );
+		}
+
+		inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");
+		if (cols > 1) {
+			inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em");
+		}
+		inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") +
+			"Class"]("ui-datepicker-multi");
+		inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") +
+			"Class"]("ui-datepicker-rtl");
+
+		if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) {
+			inst.input.focus();
+		}
+
+		// deffered render of the years select (to avoid flashes on Firefox)
+		if( inst.yearshtml ){
+			origyearshtml = inst.yearshtml;
+			setTimeout(function(){
+				//assure that inst.yearshtml didn't change.
+				if( origyearshtml === inst.yearshtml && inst.yearshtml ){
+					inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml);
+				}
+				origyearshtml = inst.yearshtml = null;
+			}, 0);
+		}
+	},
+
+	// #6694 - don't focus the input if it's already focused
+	// this breaks the change event in IE
+	// Support: IE and jQuery <1.9
+	_shouldFocusInput: function( inst ) {
+		return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" );
+	},
+
+	/* Check positioning to remain on screen. */
+	_checkOffset: function(inst, offset, isFixed) {
+		var dpWidth = inst.dpDiv.outerWidth(),
+			dpHeight = inst.dpDiv.outerHeight(),
+			inputWidth = inst.input ? inst.input.outerWidth() : 0,
+			inputHeight = inst.input ? inst.input.outerHeight() : 0,
+			viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()),
+			viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop());
+
+		offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0);
+		offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0;
+		offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0;
+
+		// now check if datepicker is showing outside window viewport - move to a better place if so.
+		offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
+			Math.abs(offset.left + dpWidth - viewWidth) : 0);
+		offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
+			Math.abs(dpHeight + inputHeight) : 0);
+
+		return offset;
+	},
+
+	/* Find an object's position on the screen. */
+	_findPos: function(obj) {
+		var position,
+			inst = this._getInst(obj),
+			isRTL = this._get(inst, "isRTL");
+
+		while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) {
+			obj = obj[isRTL ? "previousSibling" : "nextSibling"];
+		}
+
+		position = $(obj).offset();
+		return [position.left, position.top];
+	},
+
+	/* Hide the date picker from view.
+	 * @param  input  element - the input field attached to the date picker
+	 */
+	_hideDatepicker: function(input) {
+		var showAnim, duration, postProcess, onClose,
+			inst = this._curInst;
+
+		if (!inst || (input && inst !== $.data(input, "datepicker"))) {
+			return;
+		}
+
+		if (this._datepickerShowing) {
+			showAnim = this._get(inst, "showAnim");
+			duration = this._get(inst, "duration");
+			postProcess = function() {
+				$.datepicker._tidyDialog(inst);
+			};
+
+			// DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed
+			if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) {
+				inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess);
+			} else {
+				inst.dpDiv[(showAnim === "slideDown" ? "slideUp" :
+					(showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess);
+			}
+
+			if (!showAnim) {
+				postProcess();
+			}
+			this._datepickerShowing = false;
+
+			onClose = this._get(inst, "onClose");
+			if (onClose) {
+				onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]);
+			}
+
+			this._lastInput = null;
+			if (this._inDialog) {
+				this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" });
+				if ($.blockUI) {
+					$.unblockUI();
+					$("body").append(this.dpDiv);
+				}
+			}
+			this._inDialog = false;
+		}
+	},
+
+	/* Tidy up after a dialog display. */
+	_tidyDialog: function(inst) {
+		inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar");
+	},
+
+	/* Close date picker if clicked elsewhere. */
+	_checkExternalClick: function(event) {
+		if (!$.datepicker._curInst) {
+			return;
+		}
+
+		var $target = $(event.target),
+			inst = $.datepicker._getInst($target[0]);
+
+		if ( ( ( $target[0].id !== $.datepicker._mainDivId &&
+				$target.parents("#" + $.datepicker._mainDivId).length === 0 &&
+				!$target.hasClass($.datepicker.markerClassName) &&
+				!$target.closest("." + $.datepicker._triggerClass).length &&
+				$.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) ||
+			( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) {
+				$.datepicker._hideDatepicker();
+		}
+	},
+
+	/* Adjust one of the date sub-fields. */
+	_adjustDate: function(id, offset, period) {
+		var target = $(id),
+			inst = this._getInst(target[0]);
+
+		if (this._isDisabledDatepicker(target[0])) {
+			return;
+		}
+		this._adjustInstDate(inst, offset +
+			(period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning
+			period);
+		this._updateDatepicker(inst);
+	},
+
+	/* Action for current link. */
+	_gotoToday: function(id) {
+		var date,
+			target = $(id),
+			inst = this._getInst(target[0]);
+
+		if (this._get(inst, "gotoCurrent") && inst.currentDay) {
+			inst.selectedDay = inst.currentDay;
+			inst.drawMonth = inst.selectedMonth = inst.currentMonth;
+			inst.drawYear = inst.selectedYear = inst.currentYear;
+		} else {
+			date = new Date();
+			inst.selectedDay = date.getDate();
+			inst.drawMonth = inst.selectedMonth = date.getMonth();
+			inst.drawYear = inst.selectedYear = date.getFullYear();
+		}
+		this._notifyChange(inst);
+		this._adjustDate(target);
+	},
+
+	/* Action for selecting a new month/year. */
+	_selectMonthYear: function(id, select, period) {
+		var target = $(id),
+			inst = this._getInst(target[0]);
+
+		inst["selected" + (period === "M" ? "Month" : "Year")] =
+		inst["draw" + (period === "M" ? "Month" : "Year")] =
+			parseInt(select.options[select.selectedIndex].value,10);
+
+		this._notifyChange(inst);
+		this._adjustDate(target);
+	},
+
+	/* Action for selecting a day. */
+	_selectDay: function(id, month, year, td) {
+		var inst,
+			target = $(id);
+
+		if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) {
+			return;
+		}
+
+		inst = this._getInst(target[0]);
+		inst.selectedDay = inst.currentDay = $("a", td).html();
+		inst.selectedMonth = inst.currentMonth = month;
+		inst.selectedYear = inst.currentYear = year;
+		this._selectDate(id, this._formatDate(inst,
+			inst.currentDay, inst.currentMonth, inst.currentYear));
+	},
+
+	/* Erase the input field and hide the date picker. */
+	_clearDate: function(id) {
+		var target = $(id);
+		this._selectDate(target, "");
+	},
+
+	/* Update the input field with the selected date. */
+	_selectDate: function(id, dateStr) {
+		var onSelect,
+			target = $(id),
+			inst = this._getInst(target[0]);
+
+		dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
+		if (inst.input) {
+			inst.input.val(dateStr);
+		}
+		this._updateAlternate(inst);
+
+		onSelect = this._get(inst, "onSelect");
+		if (onSelect) {
+			onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);  // trigger custom callback
+		} else if (inst.input) {
+			inst.input.trigger("change"); // fire the change event
+		}
+
+		if (inst.inline){
+			this._updateDatepicker(inst);
+		} else {
+			this._hideDatepicker();
+			this._lastInput = inst.input[0];
+			if (typeof(inst.input[0]) !== "object") {
+				inst.input.focus(); // restore focus
+			}
+			this._lastInput = null;
+		}
+	},
+
+	/* Update any alternate field to synchronise with the main field. */
+	_updateAlternate: function(inst) {
+		var altFormat, date, dateStr,
+			altField = this._get(inst, "altField");
+
+		if (altField) { // update alternate field too
+			altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat");
+			date = this._getDate(inst);
+			dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst));
+			$(altField).each(function() { $(this).val(dateStr); });
+		}
+	},
+
+	/* Set as beforeShowDay function to prevent selection of weekends.
+	 * @param  date  Date - the date to customise
+	 * @return [boolean, string] - is this date selectable?, what is its CSS class?
+	 */
+	noWeekends: function(date) {
+		var day = date.getDay();
+		return [(day > 0 && day < 6), ""];
+	},
+
+	/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
+	 * @param  date  Date - the date to get the week for
+	 * @return  number - the number of the week within the year that contains this date
+	 */
+	iso8601Week: function(date) {
+		var time,
+			checkDate = new Date(date.getTime());
+
+		// Find Thursday of this week starting on Monday
+		checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
+
+		time = checkDate.getTime();
+		checkDate.setMonth(0); // Compare with Jan 1
+		checkDate.setDate(1);
+		return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
+	},
+
+	/* Parse a string value into a date object.
+	 * See formatDate below for the possible formats.
+	 *
+	 * @param  format string - the expected format of the date
+	 * @param  value string - the date in the above format
+	 * @param  settings Object - attributes include:
+	 *					shortYearCutoff  number - the cutoff year for determining the century (optional)
+	 *					dayNamesShort	string[7] - abbreviated names of the days from Sunday (optional)
+	 *					dayNames		string[7] - names of the days from Sunday (optional)
+	 *					monthNamesShort string[12] - abbreviated names of the months (optional)
+	 *					monthNames		string[12] - names of the months (optional)
+	 * @return  Date - the extracted date value or null if value is blank
+	 */
+	parseDate: function (format, value, settings) {
+		if (format == null || value == null) {
+			throw "Invalid arguments";
+		}
+
+		value = (typeof value === "object" ? value.toString() : value + "");
+		if (value === "") {
+			return null;
+		}
+
+		var iFormat, dim, extra,
+			iValue = 0,
+			shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff,
+			shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp :
+				new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)),
+			dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort,
+			dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames,
+			monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort,
+			monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames,
+			year = -1,
+			month = -1,
+			day = -1,
+			doy = -1,
+			literal = false,
+			date,
+			// Check whether a format character is doubled
+			lookAhead = function(match) {
+				var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
+				if (matches) {
+					iFormat++;
+				}
+				return matches;
+			},
+			// Extract a number from the string value
+			getNumber = function(match) {
+				var isDoubled = lookAhead(match),
+					size = (match === "@" ? 14 : (match === "!" ? 20 :
+					(match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))),
+					minSize = (match === "y" ? size : 1),
+					digits = new RegExp("^\\d{" + minSize + "," + size + "}"),
+					num = value.substring(iValue).match(digits);
+				if (!num) {
+					throw "Missing number at position " + iValue;
+				}
+				iValue += num[0].length;
+				return parseInt(num[0], 10);
+			},
+			// Extract a name from the string value and convert to an index
+			getName = function(match, shortNames, longNames) {
+				var index = -1,
+					names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) {
+						return [ [k, v] ];
+					}).sort(function (a, b) {
+						return -(a[1].length - b[1].length);
+					});
+
+				$.each(names, function (i, pair) {
+					var name = pair[1];
+					if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) {
+						index = pair[0];
+						iValue += name.length;
+						return false;
+					}
+				});
+				if (index !== -1) {
+					return index + 1;
+				} else {
+					throw "Unknown name at position " + iValue;
+				}
+			},
+			// Confirm that a literal character matches the string value
+			checkLiteral = function() {
+				if (value.charAt(iValue) !== format.charAt(iFormat)) {
+					throw "Unexpected literal at position " + iValue;
+				}
+				iValue++;
+			};
+
+		for (iFormat = 0; iFormat < format.length; iFormat++) {
+			if (literal) {
+				if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
+					literal = false;
+				} else {
+					checkLiteral();
+				}
+			} else {
+				switch (format.charAt(iFormat)) {
+					case "d":
+						day = getNumber("d");
+						break;
+					case "D":
+						getName("D", dayNamesShort, dayNames);
+						break;
+					case "o":
+						doy = getNumber("o");
+						break;
+					case "m":
+						month = getNumber("m");
+						break;
+					case "M":
+						month = getName("M", monthNamesShort, monthNames);
+						break;
+					case "y":
+						year = getNumber("y");
+						break;
+					case "@":
+						date = new Date(getNumber("@"));
+						year = date.getFullYear();
+						month = date.getMonth() + 1;
+						day = date.getDate();
+						break;
+					case "!":
+						date = new Date((getNumber("!") - this._ticksTo1970) / 10000);
+						year = date.getFullYear();
+						month = date.getMonth() + 1;
+						day = date.getDate();
+						break;
+					case "'":
+						if (lookAhead("'")){
+							checkLiteral();
+						} else {
+							literal = true;
+						}
+						break;
+					default:
+						checkLiteral();
+				}
+			}
+		}
+
+		if (iValue < value.length){
+			extra = value.substr(iValue);
+			if (!/^\s+/.test(extra)) {
+				throw "Extra/unparsed characters found in date: " + extra;
+			}
+		}
+
+		if (year === -1) {
+			year = new Date().getFullYear();
+		} else if (year < 100) {
+			year += new Date().getFullYear() - new Date().getFullYear() % 100 +
+				(year <= shortYearCutoff ? 0 : -100);
+		}
+
+		if (doy > -1) {
+			month = 1;
+			day = doy;
+			do {
+				dim = this._getDaysInMonth(year, month - 1);
+				if (day <= dim) {
+					break;
+				}
+				month++;
+				day -= dim;
+			} while (true);
+		}
+
+		date = this._daylightSavingAdjust(new Date(year, month - 1, day));
+		if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) {
+			throw "Invalid date"; // E.g. 31/02/00
+		}
+		return date;
+	},
+
+	/* Standard date formats. */
+	ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601)
+	COOKIE: "D, dd M yy",
+	ISO_8601: "yy-mm-dd",
+	RFC_822: "D, d M y",
+	RFC_850: "DD, dd-M-y",
+	RFC_1036: "D, d M y",
+	RFC_1123: "D, d M yy",
+	RFC_2822: "D, d M yy",
+	RSS: "D, d M y", // RFC 822
+	TICKS: "!",
+	TIMESTAMP: "@",
+	W3C: "yy-mm-dd", // ISO 8601
+
+	_ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
+		Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000),
+
+	/* Format a date object into a string value.
+	 * The format can be combinations of the following:
+	 * d  - day of month (no leading zero)
+	 * dd - day of month (two digit)
+	 * o  - day of year (no leading zeros)
+	 * oo - day of year (three digit)
+	 * D  - day name short
+	 * DD - day name long
+	 * m  - month of year (no leading zero)
+	 * mm - month of year (two digit)
+	 * M  - month name short
+	 * MM - month name long
+	 * y  - year (two digit)
+	 * yy - year (four digit)
+	 * @ - Unix timestamp (ms since 01/01/1970)
+	 * ! - Windows ticks (100ns since 01/01/0001)
+	 * "..." - literal text
+	 * '' - single quote
+	 *
+	 * @param  format string - the desired format of the date
+	 * @param  date Date - the date value to format
+	 * @param  settings Object - attributes include:
+	 *					dayNamesShort	string[7] - abbreviated names of the days from Sunday (optional)
+	 *					dayNames		string[7] - names of the days from Sunday (optional)
+	 *					monthNamesShort string[12] - abbreviated names of the months (optional)
+	 *					monthNames		string[12] - names of the months (optional)
+	 * @return  string - the date in the above format
+	 */
+	formatDate: function (format, date, settings) {
+		if (!date) {
+			return "";
+		}
+
+		var iFormat,
+			dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort,
+			dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames,
+			monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort,
+			monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames,
+			// Check whether a format character is doubled
+			lookAhead = function(match) {
+				var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
+				if (matches) {
+					iFormat++;
+				}
+				return matches;
+			},
+			// Format a number, with leading zero if necessary
+			formatNumber = function(match, value, len) {
+				var num = "" + value;
+				if (lookAhead(match)) {
+					while (num.length < len) {
+						num = "0" + num;
+					}
+				}
+				return num;
+			},
+			// Format a name, short or long as requested
+			formatName = function(match, value, shortNames, longNames) {
+				return (lookAhead(match) ? longNames[value] : shortNames[value]);
+			},
+			output = "",
+			literal = false;
+
+		if (date) {
+			for (iFormat = 0; iFormat < format.length; iFormat++) {
+				if (literal) {
+					if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
+						literal = false;
+					} else {
+						output += format.charAt(iFormat);
+					}
+				} else {
+					switch (format.charAt(iFormat)) {
+						case "d":
+							output += formatNumber("d", date.getDate(), 2);
+							break;
+						case "D":
+							output += formatName("D", date.getDay(), dayNamesShort, dayNames);
+							break;
+						case "o":
+							output += formatNumber("o",
+								Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3);
+							break;
+						case "m":
+							output += formatNumber("m", date.getMonth() + 1, 2);
+							break;
+						case "M":
+							output += formatName("M", date.getMonth(), monthNamesShort, monthNames);
+							break;
+						case "y":
+							output += (lookAhead("y") ? date.getFullYear() :
+								(date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100);
+							break;
+						case "@":
+							output += date.getTime();
+							break;
+						case "!":
+							output += date.getTime() * 10000 + this._ticksTo1970;
+							break;
+						case "'":
+							if (lookAhead("'")) {
+								output += "'";
+							} else {
+								literal = true;
+							}
+							break;
+						default:
+							output += format.charAt(iFormat);
+					}
+				}
+			}
+		}
+		return output;
+	},
+
+	/* Extract all possible characters from the date format. */
+	_possibleChars: function (format) {
+		var iFormat,
+			chars = "",
+			literal = false,
+			// Check whether a format character is doubled
+			lookAhead = function(match) {
+				var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
+				if (matches) {
+					iFormat++;
+				}
+				return matches;
+			};
+
+		for (iFormat = 0; iFormat < format.length; iFormat++) {
+			if (literal) {
+				if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
+					literal = false;
+				} else {
+					chars += format.charAt(iFormat);
+				}
+			} else {
+				switch (format.charAt(iFormat)) {
+					case "d": case "m": case "y": case "@":
+						chars += "0123456789";
+						break;
+					case "D": case "M":
+						return null; // Accept anything
+					case "'":
+						if (lookAhead("'")) {
+							chars += "'";
+						} else {
+							literal = true;
+						}
+						break;
+					default:
+						chars += format.charAt(iFormat);
+				}
+			}
+		}
+		return chars;
+	},
+
+	/* Get a setting value, defaulting if necessary. */
+	_get: function(inst, name) {
+		return inst.settings[name] !== undefined ?
+			inst.settings[name] : this._defaults[name];
+	},
+
+	/* Parse existing date and initialise date picker. */
+	_setDateFromField: function(inst, noDefault) {
+		if (inst.input.val() === inst.lastVal) {
+			return;
+		}
+
+		var dateFormat = this._get(inst, "dateFormat"),
+			dates = inst.lastVal = inst.input ? inst.input.val() : null,
+			defaultDate = this._getDefaultDate(inst),
+			date = defaultDate,
+			settings = this._getFormatConfig(inst);
+
+		try {
+			date = this.parseDate(dateFormat, dates, settings) || defaultDate;
+		} catch (event) {
+			dates = (noDefault ? "" : dates);
+		}
+		inst.selectedDay = date.getDate();
+		inst.drawMonth = inst.selectedMonth = date.getMonth();
+		inst.drawYear = inst.selectedYear = date.getFullYear();
+		inst.currentDay = (dates ? date.getDate() : 0);
+		inst.currentMonth = (dates ? date.getMonth() : 0);
+		inst.currentYear = (dates ? date.getFullYear() : 0);
+		this._adjustInstDate(inst);
+	},
+
+	/* Retrieve the default date shown on opening. */
+	_getDefaultDate: function(inst) {
+		return this._restrictMinMax(inst,
+			this._determineDate(inst, this._get(inst, "defaultDate"), new Date()));
+	},
+
+	/* A date may be specified as an exact value or a relative one. */
+	_determineDate: function(inst, date, defaultDate) {
+		var offsetNumeric = function(offset) {
+				var date = new Date();
+				date.setDate(date.getDate() + offset);
+				return date;
+			},
+			offsetString = function(offset) {
+				try {
+					return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"),
+						offset, $.datepicker._getFormatConfig(inst));
+				}
+				catch (e) {
+					// Ignore
+				}
+
+				var date = (offset.toLowerCase().match(/^c/) ?
+					$.datepicker._getDate(inst) : null) || new Date(),
+					year = date.getFullYear(),
+					month = date.getMonth(),
+					day = date.getDate(),
+					pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,
+					matches = pattern.exec(offset);
+
+				while (matches) {
+					switch (matches[2] || "d") {
+						case "d" : case "D" :
+							day += parseInt(matches[1],10); break;
+						case "w" : case "W" :
+							day += parseInt(matches[1],10) * 7; break;
+						case "m" : case "M" :
+							month += parseInt(matches[1],10);
+							day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
+							break;
+						case "y": case "Y" :
+							year += parseInt(matches[1],10);
+							day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
+							break;
+					}
+					matches = pattern.exec(offset);
+				}
+				return new Date(year, month, day);
+			},
+			newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) :
+				(typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime()))));
+
+		newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate);
+		if (newDate) {
+			newDate.setHours(0);
+			newDate.setMinutes(0);
+			newDate.setSeconds(0);
+			newDate.setMilliseconds(0);
+		}
+		return this._daylightSavingAdjust(newDate);
+	},
+
+	/* Handle switch to/from daylight saving.
+	 * Hours may be non-zero on daylight saving cut-over:
+	 * > 12 when midnight changeover, but then cannot generate
+	 * midnight datetime, so jump to 1AM, otherwise reset.
+	 * @param  date  (Date) the date to check
+	 * @return  (Date) the corrected date
+	 */
+	_daylightSavingAdjust: function(date) {
+		if (!date) {
+			return null;
+		}
+		date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
+		return date;
+	},
+
+	/* Set the date(s) directly. */
+	_setDate: function(inst, date, noChange) {
+		var clear = !date,
+			origMonth = inst.selectedMonth,
+			origYear = inst.selectedYear,
+			newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date()));
+
+		inst.selectedDay = inst.currentDay = newDate.getDate();
+		inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();
+		inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();
+		if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) {
+			this._notifyChange(inst);
+		}
+		this._adjustInstDate(inst);
+		if (inst.input) {
+			inst.input.val(clear ? "" : this._formatDate(inst));
+		}
+	},
+
+	/* Retrieve the date(s) directly. */
+	_getDate: function(inst) {
+		var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null :
+			this._daylightSavingAdjust(new Date(
+			inst.currentYear, inst.currentMonth, inst.currentDay)));
+			return startDate;
+	},
+
+	/* Attach the onxxx handlers.  These are declared statically so
+	 * they work with static code transformers like Caja.
+	 */
+	_attachHandlers: function(inst) {
+		var stepMonths = this._get(inst, "stepMonths"),
+			id = "#" + inst.id.replace( /\\\\/g, "\\" );
+		inst.dpDiv.find("[data-handler]").map(function () {
+			var handler = {
+				prev: function () {
+					$.datepicker._adjustDate(id, -stepMonths, "M");
+				},
+				next: function () {
+					$.datepicker._adjustDate(id, +stepMonths, "M");
+				},
+				hide: function () {
+					$.datepicker._hideDatepicker();
+				},
+				today: function () {
+					$.datepicker._gotoToday(id);
+				},
+				selectDay: function () {
+					$.datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this);
+					return false;
+				},
+				selectMonth: function () {
+					$.datepicker._selectMonthYear(id, this, "M");
+					return false;
+				},
+				selectYear: function () {
+					$.datepicker._selectMonthYear(id, this, "Y");
+					return false;
+				}
+			};
+			$(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]);
+		});
+	},
+
+	/* Generate the HTML for the current state of the date picker. */
+	_generateHTML: function(inst) {
+		var maxDraw, prevText, prev, nextText, next, currentText, gotoDate,
+			controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin,
+			monthNames, monthNamesShort, beforeShowDay, showOtherMonths,
+			selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate,
+			cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows,
+			printDate, dRow, tbody, daySettings, otherMonth, unselectable,
+			tempDate = new Date(),
+			today = this._daylightSavingAdjust(
+				new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time
+			isRTL = this._get(inst, "isRTL"),
+			showButtonPanel = this._get(inst, "showButtonPanel"),
+			hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"),
+			navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"),
+			numMonths = this._getNumberOfMonths(inst),
+			showCurrentAtPos = this._get(inst, "showCurrentAtPos"),
+			stepMonths = this._get(inst, "stepMonths"),
+			isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1),
+			currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) :
+				new Date(inst.currentYear, inst.currentMonth, inst.currentDay))),
+			minDate = this._getMinMaxDate(inst, "min"),
+			maxDate = this._getMinMaxDate(inst, "max"),
+			drawMonth = inst.drawMonth - showCurrentAtPos,
+			drawYear = inst.drawYear;
+
+		if (drawMonth < 0) {
+			drawMonth += 12;
+			drawYear--;
+		}
+		if (maxDate) {
+			maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(),
+				maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate()));
+			maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw);
+			while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) {
+				drawMonth--;
+				if (drawMonth < 0) {
+					drawMonth = 11;
+					drawYear--;
+				}
+			}
+		}
+		inst.drawMonth = drawMonth;
+		inst.drawYear = drawYear;
+
+		prevText = this._get(inst, "prevText");
+		prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText,
+			this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)),
+			this._getFormatConfig(inst)));
+
+		prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ?
+			"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" +
+			" title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>" :
+			(hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='"+ prevText +"'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>"));
+
+		nextText = this._get(inst, "nextText");
+		nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText,
+			this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)),
+			this._getFormatConfig(inst)));
+
+		next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ?
+			"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" +
+			" title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>" :
+			(hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='"+ nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>"));
+
+		currentText = this._get(inst, "currentText");
+		gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today);
+		currentText = (!navigationAsDateFormat ? currentText :
+			this.formatDate(currentText, gotoDate, this._getFormatConfig(inst)));
+
+		controls = (!inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" +
+			this._get(inst, "closeText") + "</button>" : "");
+
+		buttonPanel = (showButtonPanel) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + (isRTL ? controls : "") +
+			(this._isInRange(inst, gotoDate) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" +
+			">" + currentText + "</button>" : "") + (isRTL ? "" : controls) + "</div>" : "";
+
+		firstDay = parseInt(this._get(inst, "firstDay"),10);
+		firstDay = (isNaN(firstDay) ? 0 : firstDay);
+
+		showWeek = this._get(inst, "showWeek");
+		dayNames = this._get(inst, "dayNames");
+		dayNamesMin = this._get(inst, "dayNamesMin");
+		monthNames = this._get(inst, "monthNames");
+		monthNamesShort = this._get(inst, "monthNamesShort");
+		beforeShowDay = this._get(inst, "beforeShowDay");
+		showOtherMonths = this._get(inst, "showOtherMonths");
+		selectOtherMonths = this._get(inst, "selectOtherMonths");
+		defaultDate = this._getDefaultDate(inst);
+		html = "";
+		dow;
+		for (row = 0; row < numMonths[0]; row++) {
+			group = "";
+			this.maxRows = 4;
+			for (col = 0; col < numMonths[1]; col++) {
+				selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay));
+				cornerClass = " ui-corner-all";
+				calender = "";
+				if (isMultiMonth) {
+					calender += "<div class='ui-datepicker-group";
+					if (numMonths[1] > 1) {
+						switch (col) {
+							case 0: calender += " ui-datepicker-group-first";
+								cornerClass = " ui-corner-" + (isRTL ? "right" : "left"); break;
+							case numMonths[1]-1: calender += " ui-datepicker-group-last";
+								cornerClass = " ui-corner-" + (isRTL ? "left" : "right"); break;
+							default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break;
+						}
+					}
+					calender += "'>";
+				}
+				calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" +
+					(/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") +
+					(/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") +
+					this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate,
+					row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers
+					"</div><table class='ui-datepicker-calendar'><thead>" +
+					"<tr>";
+				thead = (showWeek ? "<th class='ui-datepicker-week-col'>" + this._get(inst, "weekHeader") + "</th>" : "");
+				for (dow = 0; dow < 7; dow++) { // days of the week
+					day = (dow + firstDay) % 7;
+					thead += "<th scope='col'" + ((dow + firstDay + 6) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "") + ">" +
+						"<span title='" + dayNames[day] + "'>" + dayNamesMin[day] + "</span></th>";
+				}
+				calender += thead + "</tr></thead><tbody>";
+				daysInMonth = this._getDaysInMonth(drawYear, drawMonth);
+				if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) {
+					inst.selectedDay = Math.min(inst.selectedDay, daysInMonth);
+				}
+				leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7;
+				curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate
+				numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043)
+				this.maxRows = numRows;
+				printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays));
+				for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows
+					calender += "<tr>";
+					tbody = (!showWeek ? "" : "<td class='ui-datepicker-week-col'>" +
+						this._get(inst, "calculateWeek")(printDate) + "</td>");
+					for (dow = 0; dow < 7; dow++) { // create date picker days
+						daySettings = (beforeShowDay ?
+							beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]);
+						otherMonth = (printDate.getMonth() !== drawMonth);
+						unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] ||
+							(minDate && printDate < minDate) || (maxDate && printDate > maxDate);
+						tbody += "<td class='" +
+							((dow + firstDay + 6) % 7 >= 5 ? " ui-datepicker-week-end" : "") + // highlight weekends
+							(otherMonth ? " ui-datepicker-other-month" : "") + // highlight days from other months
+							((printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent) || // user pressed key
+							(defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime()) ?
+							// or defaultDate is current printedDate and defaultDate is selectedDate
+							" " + this._dayOverClass : "") + // highlight selected day
+							(unselectable ? " " + this._unselectableClass + " ui-state-disabled": "") +  // highlight unselectable days
+							(otherMonth && !showOtherMonths ? "" : " " + daySettings[1] + // highlight custom dates
+							(printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "") + // highlight selected day
+							(printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "")) + "'" + // highlight today (if different)
+							((!otherMonth || showOtherMonths) && daySettings[2] ? " title='" + daySettings[2].replace(/'/g, "&#39;") + "'" : "") + // cell title
+							(unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'") + ">" + // actions
+							(otherMonth && !showOtherMonths ? "&#xa0;" : // display for other months
+							(unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" +
+							(printDate.getTime() === today.getTime() ? " ui-state-highlight" : "") +
+							(printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "") + // highlight selected day
+							(otherMonth ? " ui-priority-secondary" : "") + // distinguish dates from other months
+							"' href='#'>" + printDate.getDate() + "</a>")) + "</td>"; // display selectable date
+						printDate.setDate(printDate.getDate() + 1);
+						printDate = this._daylightSavingAdjust(printDate);
+					}
+					calender += tbody + "</tr>";
+				}
+				drawMonth++;
+				if (drawMonth > 11) {
+					drawMonth = 0;
+					drawYear++;
+				}
+				calender += "</tbody></table>" + (isMultiMonth ? "</div>" +
+							((numMonths[0] > 0 && col === numMonths[1]-1) ? "<div class='ui-datepicker-row-break'></div>" : "") : "");
+				group += calender;
+			}
+			html += group;
+		}
+		html += buttonPanel;
+		inst._keyEvent = false;
+		return html;
+	},
+
+	/* Generate the month and year header. */
+	_generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate,
+			secondary, monthNames, monthNamesShort) {
+
+		var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear,
+			changeMonth = this._get(inst, "changeMonth"),
+			changeYear = this._get(inst, "changeYear"),
+			showMonthAfterYear = this._get(inst, "showMonthAfterYear"),
+			html = "<div class='ui-datepicker-title'>",
+			monthHtml = "";
+
+		// month selection
+		if (secondary || !changeMonth) {
+			monthHtml += "<span class='ui-datepicker-month'>" + monthNames[drawMonth] + "</span>";
+		} else {
+			inMinYear = (minDate && minDate.getFullYear() === drawYear);
+			inMaxYear = (maxDate && maxDate.getFullYear() === drawYear);
+			monthHtml += "<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>";
+			for ( month = 0; month < 12; month++) {
+				if ((!inMinYear || month >= minDate.getMonth()) && (!inMaxYear || month <= maxDate.getMonth())) {
+					monthHtml += "<option value='" + month + "'" +
+						(month === drawMonth ? " selected='selected'" : "") +
+						">" + monthNamesShort[month] + "</option>";
+				}
+			}
+			monthHtml += "</select>";
+		}
+
+		if (!showMonthAfterYear) {
+			html += monthHtml + (secondary || !(changeMonth && changeYear) ? "&#xa0;" : "");
+		}
+
+		// year selection
+		if ( !inst.yearshtml ) {
+			inst.yearshtml = "";
+			if (secondary || !changeYear) {
+				html += "<span class='ui-datepicker-year'>" + drawYear + "</span>";
+			} else {
+				// determine range of years to display
+				years = this._get(inst, "yearRange").split(":");
+				thisYear = new Date().getFullYear();
+				determineYear = function(value) {
+					var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) :
+						(value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) :
+						parseInt(value, 10)));
+					return (isNaN(year) ? thisYear : year);
+				};
+				year = determineYear(years[0]);
+				endYear = Math.max(year, determineYear(years[1] || ""));
+				year = (minDate ? Math.max(year, minDate.getFullYear()) : year);
+				endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);
+				inst.yearshtml += "<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>";
+				for (; year <= endYear; year++) {
+					inst.yearshtml += "<option value='" + year + "'" +
+						(year === drawYear ? " selected='selected'" : "") +
+						">" + year + "</option>";
+				}
+				inst.yearshtml += "</select>";
+
+				html += inst.yearshtml;
+				inst.yearshtml = null;
+			}
+		}
+
+		html += this._get(inst, "yearSuffix");
+		if (showMonthAfterYear) {
+			html += (secondary || !(changeMonth && changeYear) ? "&#xa0;" : "") + monthHtml;
+		}
+		html += "</div>"; // Close datepicker_header
+		return html;
+	},
+
+	/* Adjust one of the date sub-fields. */
+	_adjustInstDate: function(inst, offset, period) {
+		var year = inst.drawYear + (period === "Y" ? offset : 0),
+			month = inst.drawMonth + (period === "M" ? offset : 0),
+			day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0),
+			date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day)));
+
+		inst.selectedDay = date.getDate();
+		inst.drawMonth = inst.selectedMonth = date.getMonth();
+		inst.drawYear = inst.selectedYear = date.getFullYear();
+		if (period === "M" || period === "Y") {
+			this._notifyChange(inst);
+		}
+	},
+
+	/* Ensure a date is within any min/max bounds. */
+	_restrictMinMax: function(inst, date) {
+		var minDate = this._getMinMaxDate(inst, "min"),
+			maxDate = this._getMinMaxDate(inst, "max"),
+			newDate = (minDate && date < minDate ? minDate : date);
+		return (maxDate && newDate > maxDate ? maxDate : newDate);
+	},
+
+	/* Notify change of month/year. */
+	_notifyChange: function(inst) {
+		var onChange = this._get(inst, "onChangeMonthYear");
+		if (onChange) {
+			onChange.apply((inst.input ? inst.input[0] : null),
+				[inst.selectedYear, inst.selectedMonth + 1, inst]);
+		}
+	},
+
+	/* Determine the number of months to show. */
+	_getNumberOfMonths: function(inst) {
+		var numMonths = this._get(inst, "numberOfMonths");
+		return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths));
+	},
+
+	/* Determine the current maximum date - ensure no time components are set. */
+	_getMinMaxDate: function(inst, minMax) {
+		return this._determineDate(inst, this._get(inst, minMax + "Date"), null);
+	},
+
+	/* Find the number of days in a given month. */
+	_getDaysInMonth: function(year, month) {
+		return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate();
+	},
+
+	/* Find the day of the week of the first of a month. */
+	_getFirstDayOfMonth: function(year, month) {
+		return new Date(year, month, 1).getDay();
+	},
+
+	/* Determines if we should allow a "next/prev" month display change. */
+	_canAdjustMonth: function(inst, offset, curYear, curMonth) {
+		var numMonths = this._getNumberOfMonths(inst),
+			date = this._daylightSavingAdjust(new Date(curYear,
+			curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1));
+
+		if (offset < 0) {
+			date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));
+		}
+		return this._isInRange(inst, date);
+	},
+
+	/* Is the given date in the accepted range? */
+	_isInRange: function(inst, date) {
+		var yearSplit, currentYear,
+			minDate = this._getMinMaxDate(inst, "min"),
+			maxDate = this._getMinMaxDate(inst, "max"),
+			minYear = null,
+			maxYear = null,
+			years = this._get(inst, "yearRange");
+			if (years){
+				yearSplit = years.split(":");
+				currentYear = new Date().getFullYear();
+				minYear = parseInt(yearSplit[0], 10);
+				maxYear = parseInt(yearSplit[1], 10);
+				if ( yearSplit[0].match(/[+\-].*/) ) {
+					minYear += currentYear;
+				}
+				if ( yearSplit[1].match(/[+\-].*/) ) {
+					maxYear += currentYear;
+				}
+			}
+
+		return ((!minDate || date.getTime() >= minDate.getTime()) &&
+			(!maxDate || date.getTime() <= maxDate.getTime()) &&
+			(!minYear || date.getFullYear() >= minYear) &&
+			(!maxYear || date.getFullYear() <= maxYear));
+	},
+
+	/* Provide the configuration settings for formatting/parsing. */
+	_getFormatConfig: function(inst) {
+		var shortYearCutoff = this._get(inst, "shortYearCutoff");
+		shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff :
+			new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
+		return {shortYearCutoff: shortYearCutoff,
+			dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"),
+			monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")};
+	},
+
+	/* Format the given date for display. */
+	_formatDate: function(inst, day, month, year) {
+		if (!day) {
+			inst.currentDay = inst.selectedDay;
+			inst.currentMonth = inst.selectedMonth;
+			inst.currentYear = inst.selectedYear;
+		}
+		var date = (day ? (typeof day === "object" ? day :
+			this._daylightSavingAdjust(new Date(year, month, day))) :
+			this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
+		return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst));
+	}
+});
+
+/*
+ * Bind hover events for datepicker elements.
+ * Done via delegate so the binding only occurs once in the lifetime of the parent div.
+ * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.
+ */
+function datepicker_bindHover(dpDiv) {
+	var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";
+	return dpDiv.delegate(selector, "mouseout", function() {
+			$(this).removeClass("ui-state-hover");
+			if (this.className.indexOf("ui-datepicker-prev") !== -1) {
+				$(this).removeClass("ui-datepicker-prev-hover");
+			}
+			if (this.className.indexOf("ui-datepicker-next") !== -1) {
+				$(this).removeClass("ui-datepicker-next-hover");
+			}
+		})
+		.delegate( selector, "mouseover", datepicker_handleMouseover );
+}
+
+function datepicker_handleMouseover() {
+	if (!$.datepicker._isDisabledDatepicker( datepicker_instActive.inline? datepicker_instActive.dpDiv.parent()[0] : datepicker_instActive.input[0])) {
+		$(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");
+		$(this).addClass("ui-state-hover");
+		if (this.className.indexOf("ui-datepicker-prev") !== -1) {
+			$(this).addClass("ui-datepicker-prev-hover");
+		}
+		if (this.className.indexOf("ui-datepicker-next") !== -1) {
+			$(this).addClass("ui-datepicker-next-hover");
+		}
+	}
+}
+
+/* jQuery extend now ignores nulls! */
+function datepicker_extendRemove(target, props) {
+	$.extend(target, props);
+	for (var name in props) {
+		if (props[name] == null) {
+			target[name] = props[name];
+		}
+	}
+	return target;
+}
+
+/* Invoke the datepicker functionality.
+   @param  options  string - a command, optionally followed by additional parameters or
+					Object - settings for attaching new datepicker functionality
+   @return  jQuery object */
+$.fn.datepicker = function(options){
+
+	/* Verify an empty collection wasn't passed - Fixes #6976 */
+	if ( !this.length ) {
+		return this;
+	}
+
+	/* Initialise the date picker. */
+	if (!$.datepicker.initialized) {
+		$(document).mousedown($.datepicker._checkExternalClick);
+		$.datepicker.initialized = true;
+	}
+
+	/* Append datepicker main container to body if not exist. */
+	if ($("#"+$.datepicker._mainDivId).length === 0) {
+		$("body").append($.datepicker.dpDiv);
+	}
+
+	var otherArgs = Array.prototype.slice.call(arguments, 1);
+	if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) {
+		return $.datepicker["_" + options + "Datepicker"].
+			apply($.datepicker, [this[0]].concat(otherArgs));
+	}
+	if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") {
+		return $.datepicker["_" + options + "Datepicker"].
+			apply($.datepicker, [this[0]].concat(otherArgs));
+	}
+	return this.each(function() {
+		typeof options === "string" ?
+			$.datepicker["_" + options + "Datepicker"].
+				apply($.datepicker, [this].concat(otherArgs)) :
+			$.datepicker._attachDatepicker(this, options);
+	});
+};
+
+$.datepicker = new Datepicker(); // singleton instance
+$.datepicker.initialized = false;
+$.datepicker.uuid = new Date().getTime();
+$.datepicker.version = "1.11.4";
+
+var datepicker = $.datepicker;
+
+
+/*!
+ * jQuery UI Dialog 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/dialog/
+ */
+
+
+var dialog = $.widget( "ui.dialog", {
+	version: "1.11.4",
+	options: {
+		appendTo: "body",
+		autoOpen: true,
+		buttons: [],
+		closeOnEscape: true,
+		closeText: "Close",
+		dialogClass: "",
+		draggable: true,
+		hide: null,
+		height: "auto",
+		maxHeight: null,
+		maxWidth: null,
+		minHeight: 150,
+		minWidth: 150,
+		modal: false,
+		position: {
+			my: "center",
+			at: "center",
+			of: window,
+			collision: "fit",
+			// Ensure the titlebar is always visible
+			using: function( pos ) {
+				var topOffset = $( this ).css( pos ).offset().top;
+				if ( topOffset < 0 ) {
+					$( this ).css( "top", pos.top - topOffset );
+				}
+			}
+		},
+		resizable: true,
+		show: null,
+		title: null,
+		width: 300,
+
+		// callbacks
+		beforeClose: null,
+		close: null,
+		drag: null,
+		dragStart: null,
+		dragStop: null,
+		focus: null,
+		open: null,
+		resize: null,
+		resizeStart: null,
+		resizeStop: null
+	},
+
+	sizeRelatedOptions: {
+		buttons: true,
+		height: true,
+		maxHeight: true,
+		maxWidth: true,
+		minHeight: true,
+		minWidth: true,
+		width: true
+	},
+
+	resizableRelatedOptions: {
+		maxHeight: true,
+		maxWidth: true,
+		minHeight: true,
+		minWidth: true
+	},
+
+	_create: function() {
+		this.originalCss = {
+			display: this.element[ 0 ].style.display,
+			width: this.element[ 0 ].style.width,
+			minHeight: this.element[ 0 ].style.minHeight,
+			maxHeight: this.element[ 0 ].style.maxHeight,
+			height: this.element[ 0 ].style.height
+		};
+		this.originalPosition = {
+			parent: this.element.parent(),
+			index: this.element.parent().children().index( this.element )
+		};
+		this.originalTitle = this.element.attr( "title" );
+		this.options.title = this.options.title || this.originalTitle;
+
+		this._createWrapper();
+
+		this.element
+			.show()
+			.removeAttr( "title" )
+			.addClass( "ui-dialog-content ui-widget-content" )
+			.appendTo( this.uiDialog );
+
+		this._createTitlebar();
+		this._createButtonPane();
+
+		if ( this.options.draggable && $.fn.draggable ) {
+			this._makeDraggable();
+		}
+		if ( this.options.resizable && $.fn.resizable ) {
+			this._makeResizable();
+		}
+
+		this._isOpen = false;
+
+		this._trackFocus();
+	},
+
+	_init: function() {
+		if ( this.options.autoOpen ) {
+			this.open();
+		}
+	},
+
+	_appendTo: function() {
+		var element = this.options.appendTo;
+		if ( element && (element.jquery || element.nodeType) ) {
+			return $( element );
+		}
+		return this.document.find( element || "body" ).eq( 0 );
+	},
+
+	_destroy: function() {
+		var next,
+			originalPosition = this.originalPosition;
+
+		this._untrackInstance();
+		this._destroyOverlay();
+
+		this.element
+			.removeUniqueId()
+			.removeClass( "ui-dialog-content ui-widget-content" )
+			.css( this.originalCss )
+			// Without detaching first, the following becomes really slow
+			.detach();
+
+		this.uiDialog.stop( true, true ).remove();
+
+		if ( this.originalTitle ) {
+			this.element.attr( "title", this.originalTitle );
+		}
+
+		next = originalPosition.parent.children().eq( originalPosition.index );
+		// Don't try to place the dialog next to itself (#8613)
+		if ( next.length && next[ 0 ] !== this.element[ 0 ] ) {
+			next.before( this.element );
+		} else {
+			originalPosition.parent.append( this.element );
+		}
+	},
+
+	widget: function() {
+		return this.uiDialog;
+	},
+
+	disable: $.noop,
+	enable: $.noop,
+
+	close: function( event ) {
+		var activeElement,
+			that = this;
+
+		if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) {
+			return;
+		}
+
+		this._isOpen = false;
+		this._focusedElement = null;
+		this._destroyOverlay();
+		this._untrackInstance();
+
+		if ( !this.opener.filter( ":focusable" ).focus().length ) {
+
+			// support: IE9
+			// IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
+			try {
+				activeElement = this.document[ 0 ].activeElement;
+
+				// Support: IE9, IE10
+				// If the <body> is blurred, IE will switch windows, see #4520
+				if ( activeElement && activeElement.nodeName.toLowerCase() !== "body" ) {
+
+					// Hiding a focused element doesn't trigger blur in WebKit
+					// so in case we have nothing to focus on, explicitly blur the active element
+					// https://bugs.webkit.org/show_bug.cgi?id=47182
+					$( activeElement ).blur();
+				}
+			} catch ( error ) {}
+		}
+
+		this._hide( this.uiDialog, this.options.hide, function() {
+			that._trigger( "close", event );
+		});
+	},
+
+	isOpen: function() {
+		return this._isOpen;
+	},
+
+	moveToTop: function() {
+		this._moveToTop();
+	},
+
+	_moveToTop: function( event, silent ) {
+		var moved = false,
+			zIndices = this.uiDialog.siblings( ".ui-front:visible" ).map(function() {
+				return +$( this ).css( "z-index" );
+			}).get(),
+			zIndexMax = Math.max.apply( null, zIndices );
+
+		if ( zIndexMax >= +this.uiDialog.css( "z-index" ) ) {
+			this.uiDialog.css( "z-index", zIndexMax + 1 );
+			moved = true;
+		}
+
+		if ( moved && !silent ) {
+			this._trigger( "focus", event );
+		}
+		return moved;
+	},
+
+	open: function() {
+		var that = this;
+		if ( this._isOpen ) {
+			if ( this._moveToTop() ) {
+				this._focusTabbable();
+			}
+			return;
+		}
+
+		this._isOpen = true;
+		this.opener = $( this.document[ 0 ].activeElement );
+
+		this._size();
+		this._position();
+		this._createOverlay();
+		this._moveToTop( null, true );
+
+		// Ensure the overlay is moved to the top with the dialog, but only when
+		// opening. The overlay shouldn't move after the dialog is open so that
+		// modeless dialogs opened after the modal dialog stack properly.
+		if ( this.overlay ) {
+			this.overlay.css( "z-index", this.uiDialog.css( "z-index" ) - 1 );
+		}
+
+		this._show( this.uiDialog, this.options.show, function() {
+			that._focusTabbable();
+			that._trigger( "focus" );
+		});
+
+		// Track the dialog immediately upon openening in case a focus event
+		// somehow occurs outside of the dialog before an element inside the
+		// dialog is focused (#10152)
+		this._makeFocusTarget();
+
+		this._trigger( "open" );
+	},
+
+	_focusTabbable: function() {
+		// Set focus to the first match:
+		// 1. An element that was focused previously
+		// 2. First element inside the dialog matching [autofocus]
+		// 3. Tabbable element inside the content element
+		// 4. Tabbable element inside the buttonpane
+		// 5. The close button
+		// 6. The dialog itself
+		var hasFocus = this._focusedElement;
+		if ( !hasFocus ) {
+			hasFocus = this.element.find( "[autofocus]" );
+		}
+		if ( !hasFocus.length ) {
+			hasFocus = this.element.find( ":tabbable" );
+		}
+		if ( !hasFocus.length ) {
+			hasFocus = this.uiDialogButtonPane.find( ":tabbable" );
+		}
+		if ( !hasFocus.length ) {
+			hasFocus = this.uiDialogTitlebarClose.filter( ":tabbable" );
+		}
+		if ( !hasFocus.length ) {
+			hasFocus = this.uiDialog;
+		}
+		hasFocus.eq( 0 ).focus();
+	},
+
+	_keepFocus: function( event ) {
+		function checkFocus() {
+			var activeElement = this.document[0].activeElement,
+				isActive = this.uiDialog[0] === activeElement ||
+					$.contains( this.uiDialog[0], activeElement );
+			if ( !isActive ) {
+				this._focusTabbable();
+			}
+		}
+		event.preventDefault();
+		checkFocus.call( this );
+		// support: IE
+		// IE <= 8 doesn't prevent moving focus even with event.preventDefault()
+		// so we check again later
+		this._delay( checkFocus );
+	},
+
+	_createWrapper: function() {
+		this.uiDialog = $("<div>")
+			.addClass( "ui-dialog ui-widget ui-widget-content ui-corner-all ui-front " +
+				this.options.dialogClass )
+			.hide()
+			.attr({
+				// Setting tabIndex makes the div focusable
+				tabIndex: -1,
+				role: "dialog"
+			})
+			.appendTo( this._appendTo() );
+
+		this._on( this.uiDialog, {
+			keydown: function( event ) {
+				if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
+						event.keyCode === $.ui.keyCode.ESCAPE ) {
+					event.preventDefault();
+					this.close( event );
+					return;
+				}
+
+				// prevent tabbing out of dialogs
+				if ( event.keyCode !== $.ui.keyCode.TAB || event.isDefaultPrevented() ) {
+					return;
+				}
+				var tabbables = this.uiDialog.find( ":tabbable" ),
+					first = tabbables.filter( ":first" ),
+					last = tabbables.filter( ":last" );
+
+				if ( ( event.target === last[0] || event.target === this.uiDialog[0] ) && !event.shiftKey ) {
+					this._delay(function() {
+						first.focus();
+					});
+					event.preventDefault();
+				} else if ( ( event.target === first[0] || event.target === this.uiDialog[0] ) && event.shiftKey ) {
+					this._delay(function() {
+						last.focus();
+					});
+					event.preventDefault();
+				}
+			},
+			mousedown: function( event ) {
+				if ( this._moveToTop( event ) ) {
+					this._focusTabbable();
+				}
+			}
+		});
+
+		// We assume that any existing aria-describedby attribute means
+		// that the dialog content is marked up properly
+		// otherwise we brute force the content as the description
+		if ( !this.element.find( "[aria-describedby]" ).length ) {
+			this.uiDialog.attr({
+				"aria-describedby": this.element.uniqueId().attr( "id" )
+			});
+		}
+	},
+
+	_createTitlebar: function() {
+		var uiDialogTitle;
+
+		this.uiDialogTitlebar = $( "<div>" )
+			.addClass( "ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix" )
+			.prependTo( this.uiDialog );
+		this._on( this.uiDialogTitlebar, {
+			mousedown: function( event ) {
+				// Don't prevent click on close button (#8838)
+				// Focusing a dialog that is partially scrolled out of view
+				// causes the browser to scroll it into view, preventing the click event
+				if ( !$( event.target ).closest( ".ui-dialog-titlebar-close" ) ) {
+					// Dialog isn't getting focus when dragging (#8063)
+					this.uiDialog.focus();
+				}
+			}
+		});
+
+		// support: IE
+		// Use type="button" to prevent enter keypresses in textboxes from closing the
+		// dialog in IE (#9312)
+		this.uiDialogTitlebarClose = $( "<button type='button'></button>" )
+			.button({
+				label: this.options.closeText,
+				icons: {
+					primary: "ui-icon-closethick"
+				},
+				text: false
+			})
+			.addClass( "ui-dialog-titlebar-close" )
+			.appendTo( this.uiDialogTitlebar );
+		this._on( this.uiDialogTitlebarClose, {
+			click: function( event ) {
+				event.preventDefault();
+				this.close( event );
+			}
+		});
+
+		uiDialogTitle = $( "<span>" )
+			.uniqueId()
+			.addClass( "ui-dialog-title" )
+			.prependTo( this.uiDialogTitlebar );
+		this._title( uiDialogTitle );
+
+		this.uiDialog.attr({
+			"aria-labelledby": uiDialogTitle.attr( "id" )
+		});
+	},
+
+	_title: function( title ) {
+		if ( !this.options.title ) {
+			title.html( "&#160;" );
+		}
+		title.text( this.options.title );
+	},
+
+	_createButtonPane: function() {
+		this.uiDialogButtonPane = $( "<div>" )
+			.addClass( "ui-dialog-buttonpane ui-widget-content ui-helper-clearfix" );
+
+		this.uiButtonSet = $( "<div>" )
+			.addClass( "ui-dialog-buttonset" )
+			.appendTo( this.uiDialogButtonPane );
+
+		this._createButtons();
+	},
+
+	_createButtons: function() {
+		var that = this,
+			buttons = this.options.buttons;
+
+		// if we already have a button pane, remove it
+		this.uiDialogButtonPane.remove();
+		this.uiButtonSet.empty();
+
+		if ( $.isEmptyObject( buttons ) || ($.isArray( buttons ) && !buttons.length) ) {
+			this.uiDialog.removeClass( "ui-dialog-buttons" );
+			return;
+		}
+
+		$.each( buttons, function( name, props ) {
+			var click, buttonOptions;
+			props = $.isFunction( props ) ?
+				{ click: props, text: name } :
+				props;
+			// Default to a non-submitting button
+			props = $.extend( { type: "button" }, props );
+			// Change the context for the click callback to be the main element
+			click = props.click;
+			props.click = function() {
+				click.apply( that.element[ 0 ], arguments );
+			};
+			buttonOptions = {
+				icons: props.icons,
+				text: props.showText
+			};
+			delete props.icons;
+			delete props.showText;
+			$( "<button></button>", props )
+				.button( buttonOptions )
+				.appendTo( that.uiButtonSet );
+		});
+		this.uiDialog.addClass( "ui-dialog-buttons" );
+		this.uiDialogButtonPane.appendTo( this.uiDialog );
+	},
+
+	_makeDraggable: function() {
+		var that = this,
+			options = this.options;
+
+		function filteredUi( ui ) {
+			return {
+				position: ui.position,
+				offset: ui.offset
+			};
+		}
+
+		this.uiDialog.draggable({
+			cancel: ".ui-dialog-content, .ui-dialog-titlebar-close",
+			handle: ".ui-dialog-titlebar",
+			containment: "document",
+			start: function( event, ui ) {
+				$( this ).addClass( "ui-dialog-dragging" );
+				that._blockFrames();
+				that._trigger( "dragStart", event, filteredUi( ui ) );
+			},
+			drag: function( event, ui ) {
+				that._trigger( "drag", event, filteredUi( ui ) );
+			},
+			stop: function( event, ui ) {
+				var left = ui.offset.left - that.document.scrollLeft(),
+					top = ui.offset.top - that.document.scrollTop();
+
+				options.position = {
+					my: "left top",
+					at: "left" + (left >= 0 ? "+" : "") + left + " " +
+						"top" + (top >= 0 ? "+" : "") + top,
+					of: that.window
+				};
+				$( this ).removeClass( "ui-dialog-dragging" );
+				that._unblockFrames();
+				that._trigger( "dragStop", event, filteredUi( ui ) );
+			}
+		});
+	},
+
+	_makeResizable: function() {
+		var that = this,
+			options = this.options,
+			handles = options.resizable,
+			// .ui-resizable has position: relative defined in the stylesheet
+			// but dialogs have to use absolute or fixed positioning
+			position = this.uiDialog.css("position"),
+			resizeHandles = typeof handles === "string" ?
+				handles	:
+				"n,e,s,w,se,sw,ne,nw";
+
+		function filteredUi( ui ) {
+			return {
+				originalPosition: ui.originalPosition,
+				originalSize: ui.originalSize,
+				position: ui.position,
+				size: ui.size
+			};
+		}
+
+		this.uiDialog.resizable({
+			cancel: ".ui-dialog-content",
+			containment: "document",
+			alsoResize: this.element,
+			maxWidth: options.maxWidth,
+			maxHeight: options.maxHeight,
+			minWidth: options.minWidth,
+			minHeight: this._minHeight(),
+			handles: resizeHandles,
+			start: function( event, ui ) {
+				$( this ).addClass( "ui-dialog-resizing" );
+				that._blockFrames();
+				that._trigger( "resizeStart", event, filteredUi( ui ) );
+			},
+			resize: function( event, ui ) {
+				that._trigger( "resize", event, filteredUi( ui ) );
+			},
+			stop: function( event, ui ) {
+				var offset = that.uiDialog.offset(),
+					left = offset.left - that.document.scrollLeft(),
+					top = offset.top - that.document.scrollTop();
+
+				options.height = that.uiDialog.height();
+				options.width = that.uiDialog.width();
+				options.position = {
+					my: "left top",
+					at: "left" + (left >= 0 ? "+" : "") + left + " " +
+						"top" + (top >= 0 ? "+" : "") + top,
+					of: that.window
+				};
+				$( this ).removeClass( "ui-dialog-resizing" );
+				that._unblockFrames();
+				that._trigger( "resizeStop", event, filteredUi( ui ) );
+			}
+		})
+		.css( "position", position );
+	},
+
+	_trackFocus: function() {
+		this._on( this.widget(), {
+			focusin: function( event ) {
+				this._makeFocusTarget();
+				this._focusedElement = $( event.target );
+			}
+		});
+	},
+
+	_makeFocusTarget: function() {
+		this._untrackInstance();
+		this._trackingInstances().unshift( this );
+	},
+
+	_untrackInstance: function() {
+		var instances = this._trackingInstances(),
+			exists = $.inArray( this, instances );
+		if ( exists !== -1 ) {
+			instances.splice( exists, 1 );
+		}
+	},
+
+	_trackingInstances: function() {
+		var instances = this.document.data( "ui-dialog-instances" );
+		if ( !instances ) {
+			instances = [];
+			this.document.data( "ui-dialog-instances", instances );
+		}
+		return instances;
+	},
+
+	_minHeight: function() {
+		var options = this.options;
+
+		return options.height === "auto" ?
+			options.minHeight :
+			Math.min( options.minHeight, options.height );
+	},
+
+	_position: function() {
+		// Need to show the dialog to get the actual offset in the position plugin
+		var isVisible = this.uiDialog.is( ":visible" );
+		if ( !isVisible ) {
+			this.uiDialog.show();
+		}
+		this.uiDialog.position( this.options.position );
+		if ( !isVisible ) {
+			this.uiDialog.hide();
+		}
+	},
+
+	_setOptions: function( options ) {
+		var that = this,
+			resize = false,
+			resizableOptions = {};
+
+		$.each( options, function( key, value ) {
+			that._setOption( key, value );
+
+			if ( key in that.sizeRelatedOptions ) {
+				resize = true;
+			}
+			if ( key in that.resizableRelatedOptions ) {
+				resizableOptions[ key ] = value;
+			}
+		});
+
+		if ( resize ) {
+			this._size();
+			this._position();
+		}
+		if ( this.uiDialog.is( ":data(ui-resizable)" ) ) {
+			this.uiDialog.resizable( "option", resizableOptions );
+		}
+	},
+
+	_setOption: function( key, value ) {
+		var isDraggable, isResizable,
+			uiDialog = this.uiDialog;
+
+		if ( key === "dialogClass" ) {
+			uiDialog
+				.removeClass( this.options.dialogClass )
+				.addClass( value );
+		}
+
+		if ( key === "disabled" ) {
+			return;
+		}
+
+		this._super( key, value );
+
+		if ( key === "appendTo" ) {
+			this.uiDialog.appendTo( this._appendTo() );
+		}
+
+		if ( key === "buttons" ) {
+			this._createButtons();
+		}
+
+		if ( key === "closeText" ) {
+			this.uiDialogTitlebarClose.button({
+				// Ensure that we always pass a string
+				label: "" + value
+			});
+		}
+
+		if ( key === "draggable" ) {
+			isDraggable = uiDialog.is( ":data(ui-draggable)" );
+			if ( isDraggable && !value ) {
+				uiDialog.draggable( "destroy" );
+			}
+
+			if ( !isDraggable && value ) {
+				this._makeDraggable();
+			}
+		}
+
+		if ( key === "position" ) {
+			this._position();
+		}
+
+		if ( key === "resizable" ) {
+			// currently resizable, becoming non-resizable
+			isResizable = uiDialog.is( ":data(ui-resizable)" );
+			if ( isResizable && !value ) {
+				uiDialog.resizable( "destroy" );
+			}
+
+			// currently resizable, changing handles
+			if ( isResizable && typeof value === "string" ) {
+				uiDialog.resizable( "option", "handles", value );
+			}
+
+			// currently non-resizable, becoming resizable
+			if ( !isResizable && value !== false ) {
+				this._makeResizable();
+			}
+		}
+
+		if ( key === "title" ) {
+			this._title( this.uiDialogTitlebar.find( ".ui-dialog-title" ) );
+		}
+	},
+
+	_size: function() {
+		// If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
+		// divs will both have width and height set, so we need to reset them
+		var nonContentHeight, minContentHeight, maxContentHeight,
+			options = this.options;
+
+		// Reset content sizing
+		this.element.show().css({
+			width: "auto",
+			minHeight: 0,
+			maxHeight: "none",
+			height: 0
+		});
+
+		if ( options.minWidth > options.width ) {
+			options.width = options.minWidth;
+		}
+
+		// reset wrapper sizing
+		// determine the height of all the non-content elements
+		nonContentHeight = this.uiDialog.css({
+				height: "auto",
+				width: options.width
+			})
+			.outerHeight();
+		minContentHeight = Math.max( 0, options.minHeight - nonContentHeight );
+		maxContentHeight = typeof options.maxHeight === "number" ?
+			Math.max( 0, options.maxHeight - nonContentHeight ) :
+			"none";
+
+		if ( options.height === "auto" ) {
+			this.element.css({
+				minHeight: minContentHeight,
+				maxHeight: maxContentHeight,
+				height: "auto"
+			});
+		} else {
+			this.element.height( Math.max( 0, options.height - nonContentHeight ) );
+		}
+
+		if ( this.uiDialog.is( ":data(ui-resizable)" ) ) {
+			this.uiDialog.resizable( "option", "minHeight", this._minHeight() );
+		}
+	},
+
+	_blockFrames: function() {
+		this.iframeBlocks = this.document.find( "iframe" ).map(function() {
+			var iframe = $( this );
+
+			return $( "<div>" )
+				.css({
+					position: "absolute",
+					width: iframe.outerWidth(),
+					height: iframe.outerHeight()
+				})
+				.appendTo( iframe.parent() )
+				.offset( iframe.offset() )[0];
+		});
+	},
+
+	_unblockFrames: function() {
+		if ( this.iframeBlocks ) {
+			this.iframeBlocks.remove();
+			delete this.iframeBlocks;
+		}
+	},
+
+	_allowInteraction: function( event ) {
+		if ( $( event.target ).closest( ".ui-dialog" ).length ) {
+			return true;
+		}
+
+		// TODO: Remove hack when datepicker implements
+		// the .ui-front logic (#8989)
+		return !!$( event.target ).closest( ".ui-datepicker" ).length;
+	},
+
+	_createOverlay: function() {
+		if ( !this.options.modal ) {
+			return;
+		}
+
+		// We use a delay in case the overlay is created from an
+		// event that we're going to be cancelling (#2804)
+		var isOpening = true;
+		this._delay(function() {
+			isOpening = false;
+		});
+
+		if ( !this.document.data( "ui-dialog-overlays" ) ) {
+
+			// Prevent use of anchors and inputs
+			// Using _on() for an event handler shared across many instances is
+			// safe because the dialogs stack and must be closed in reverse order
+			this._on( this.document, {
+				focusin: function( event ) {
+					if ( isOpening ) {
+						return;
+					}
+
+					if ( !this._allowInteraction( event ) ) {
+						event.preventDefault();
+						this._trackingInstances()[ 0 ]._focusTabbable();
+					}
+				}
+			});
+		}
+
+		this.overlay = $( "<div>" )
+			.addClass( "ui-widget-overlay ui-front" )
+			.appendTo( this._appendTo() );
+		this._on( this.overlay, {
+			mousedown: "_keepFocus"
+		});
+		this.document.data( "ui-dialog-overlays",
+			(this.document.data( "ui-dialog-overlays" ) || 0) + 1 );
+	},
+
+	_destroyOverlay: function() {
+		if ( !this.options.modal ) {
+			return;
+		}
+
+		if ( this.overlay ) {
+			var overlays = this.document.data( "ui-dialog-overlays" ) - 1;
+
+			if ( !overlays ) {
+				this.document
+					.unbind( "focusin" )
+					.removeData( "ui-dialog-overlays" );
+			} else {
+				this.document.data( "ui-dialog-overlays", overlays );
+			}
+
+			this.overlay.remove();
+			this.overlay = null;
+		}
+	}
+});
+
+
+/*!
+ * jQuery UI Progressbar 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/progressbar/
+ */
+
+
+var progressbar = $.widget( "ui.progressbar", {
+	version: "1.11.4",
+	options: {
+		max: 100,
+		value: 0,
+
+		change: null,
+		complete: null
+	},
+
+	min: 0,
+
+	_create: function() {
+		// Constrain initial value
+		this.oldValue = this.options.value = this._constrainedValue();
+
+		this.element
+			.addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
+			.attr({
+				// Only set static values, aria-valuenow and aria-valuemax are
+				// set inside _refreshValue()
+				role: "progressbar",
+				"aria-valuemin": this.min
+			});
+
+		this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" )
+			.appendTo( this.element );
+
+		this._refreshValue();
+	},
+
+	_destroy: function() {
+		this.element
+			.removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
+			.removeAttr( "role" )
+			.removeAttr( "aria-valuemin" )
+			.removeAttr( "aria-valuemax" )
+			.removeAttr( "aria-valuenow" );
+
+		this.valueDiv.remove();
+	},
+
+	value: function( newValue ) {
+		if ( newValue === undefined ) {
+			return this.options.value;
+		}
+
+		this.options.value = this._constrainedValue( newValue );
+		this._refreshValue();
+	},
+
+	_constrainedValue: function( newValue ) {
+		if ( newValue === undefined ) {
+			newValue = this.options.value;
+		}
+
+		this.indeterminate = newValue === false;
+
+		// sanitize value
+		if ( typeof newValue !== "number" ) {
+			newValue = 0;
+		}
+
+		return this.indeterminate ? false :
+			Math.min( this.options.max, Math.max( this.min, newValue ) );
+	},
+
+	_setOptions: function( options ) {
+		// Ensure "value" option is set after other values (like max)
+		var value = options.value;
+		delete options.value;
+
+		this._super( options );
+
+		this.options.value = this._constrainedValue( value );
+		this._refreshValue();
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "max" ) {
+			// Don't allow a max less than min
+			value = Math.max( this.min, value );
+		}
+		if ( key === "disabled" ) {
+			this.element
+				.toggleClass( "ui-state-disabled", !!value )
+				.attr( "aria-disabled", value );
+		}
+		this._super( key, value );
+	},
+
+	_percentage: function() {
+		return this.indeterminate ? 100 : 100 * ( this.options.value - this.min ) / ( this.options.max - this.min );
+	},
+
+	_refreshValue: function() {
+		var value = this.options.value,
+			percentage = this._percentage();
+
+		this.valueDiv
+			.toggle( this.indeterminate || value > this.min )
+			.toggleClass( "ui-corner-right", value === this.options.max )
+			.width( percentage.toFixed(0) + "%" );
+
+		this.element.toggleClass( "ui-progressbar-indeterminate", this.indeterminate );
+
+		if ( this.indeterminate ) {
+			this.element.removeAttr( "aria-valuenow" );
+			if ( !this.overlayDiv ) {
+				this.overlayDiv = $( "<div class='ui-progressbar-overlay'></div>" ).appendTo( this.valueDiv );
+			}
+		} else {
+			this.element.attr({
+				"aria-valuemax": this.options.max,
+				"aria-valuenow": value
+			});
+			if ( this.overlayDiv ) {
+				this.overlayDiv.remove();
+				this.overlayDiv = null;
+			}
+		}
+
+		if ( this.oldValue !== value ) {
+			this.oldValue = value;
+			this._trigger( "change" );
+		}
+		if ( value === this.options.max ) {
+			this._trigger( "complete" );
+		}
+	}
+});
+
+
+/*!
+ * jQuery UI Selectmenu 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/selectmenu
+ */
+
+
+var selectmenu = $.widget( "ui.selectmenu", {
+	version: "1.11.4",
+	defaultElement: "<select>",
+	options: {
+		appendTo: null,
+		disabled: null,
+		icons: {
+			button: "ui-icon-triangle-1-s"
+		},
+		position: {
+			my: "left top",
+			at: "left bottom",
+			collision: "none"
+		},
+		width: null,
+
+		// callbacks
+		change: null,
+		close: null,
+		focus: null,
+		open: null,
+		select: null
+	},
+
+	_create: function() {
+		var selectmenuId = this.element.uniqueId().attr( "id" );
+		this.ids = {
+			element: selectmenuId,
+			button: selectmenuId + "-button",
+			menu: selectmenuId + "-menu"
+		};
+
+		this._drawButton();
+		this._drawMenu();
+
+		if ( this.options.disabled ) {
+			this.disable();
+		}
+	},
+
+	_drawButton: function() {
+		var that = this;
+
+		// Associate existing label with the new button
+		this.label = $( "label[for='" + this.ids.element + "']" ).attr( "for", this.ids.button );
+		this._on( this.label, {
+			click: function( event ) {
+				this.button.focus();
+				event.preventDefault();
+			}
+		});
+
+		// Hide original select element
+		this.element.hide();
+
+		// Create button
+		this.button = $( "<span>", {
+			"class": "ui-selectmenu-button ui-widget ui-state-default ui-corner-all",
+			tabindex: this.options.disabled ? -1 : 0,
+			id: this.ids.button,
+			role: "combobox",
+			"aria-expanded": "false",
+			"aria-autocomplete": "list",
+			"aria-owns": this.ids.menu,
+			"aria-haspopup": "true"
+		})
+			.insertAfter( this.element );
+
+		$( "<span>", {
+			"class": "ui-icon " + this.options.icons.button
+		})
+			.prependTo( this.button );
+
+		this.buttonText = $( "<span>", {
+			"class": "ui-selectmenu-text"
+		})
+			.appendTo( this.button );
+
+		this._setText( this.buttonText, this.element.find( "option:selected" ).text() );
+		this._resizeButton();
+
+		this._on( this.button, this._buttonEvents );
+		this.button.one( "focusin", function() {
+
+			// Delay rendering the menu items until the button receives focus.
+			// The menu may have already been rendered via a programmatic open.
+			if ( !that.menuItems ) {
+				that._refreshMenu();
+			}
+		});
+		this._hoverable( this.button );
+		this._focusable( this.button );
+	},
+
+	_drawMenu: function() {
+		var that = this;
+
+		// Create menu
+		this.menu = $( "<ul>", {
+			"aria-hidden": "true",
+			"aria-labelledby": this.ids.button,
+			id: this.ids.menu
+		});
+
+		// Wrap menu
+		this.menuWrap = $( "<div>", {
+			"class": "ui-selectmenu-menu ui-front"
+		})
+			.append( this.menu )
+			.appendTo( this._appendTo() );
+
+		// Initialize menu widget
+		this.menuInstance = this.menu
+			.menu({
+				role: "listbox",
+				select: function( event, ui ) {
+					event.preventDefault();
+
+					// support: IE8
+					// If the item was selected via a click, the text selection
+					// will be destroyed in IE
+					that._setSelection();
+
+					that._select( ui.item.data( "ui-selectmenu-item" ), event );
+				},
+				focus: function( event, ui ) {
+					var item = ui.item.data( "ui-selectmenu-item" );
+
+					// Prevent inital focus from firing and check if its a newly focused item
+					if ( that.focusIndex != null && item.index !== that.focusIndex ) {
+						that._trigger( "focus", event, { item: item } );
+						if ( !that.isOpen ) {
+							that._select( item, event );
+						}
+					}
+					that.focusIndex = item.index;
+
+					that.button.attr( "aria-activedescendant",
+						that.menuItems.eq( item.index ).attr( "id" ) );
+				}
+			})
+			.menu( "instance" );
+
+		// Adjust menu styles to dropdown
+		this.menu
+			.addClass( "ui-corner-bottom" )
+			.removeClass( "ui-corner-all" );
+
+		// Don't close the menu on mouseleave
+		this.menuInstance._off( this.menu, "mouseleave" );
+
+		// Cancel the menu's collapseAll on document click
+		this.menuInstance._closeOnDocumentClick = function() {
+			return false;
+		};
+
+		// Selects often contain empty items, but never contain dividers
+		this.menuInstance._isDivider = function() {
+			return false;
+		};
+	},
+
+	refresh: function() {
+		this._refreshMenu();
+		this._setText( this.buttonText, this._getSelectedItem().text() );
+		if ( !this.options.width ) {
+			this._resizeButton();
+		}
+	},
+
+	_refreshMenu: function() {
+		this.menu.empty();
+
+		var item,
+			options = this.element.find( "option" );
+
+		if ( !options.length ) {
+			return;
+		}
+
+		this._parseOptions( options );
+		this._renderMenu( this.menu, this.items );
+
+		this.menuInstance.refresh();
+		this.menuItems = this.menu.find( "li" ).not( ".ui-selectmenu-optgroup" );
+
+		item = this._getSelectedItem();
+
+		// Update the menu to have the correct item focused
+		this.menuInstance.focus( null, item );
+		this._setAria( item.data( "ui-selectmenu-item" ) );
+
+		// Set disabled state
+		this._setOption( "disabled", this.element.prop( "disabled" ) );
+	},
+
+	open: function( event ) {
+		if ( this.options.disabled ) {
+			return;
+		}
+
+		// If this is the first time the menu is being opened, render the items
+		if ( !this.menuItems ) {
+			this._refreshMenu();
+		} else {
+
+			// Menu clears focus on close, reset focus to selected item
+			this.menu.find( ".ui-state-focus" ).removeClass( "ui-state-focus" );
+			this.menuInstance.focus( null, this._getSelectedItem() );
+		}
+
+		this.isOpen = true;
+		this._toggleAttr();
+		this._resizeMenu();
+		this._position();
+
+		this._on( this.document, this._documentClick );
+
+		this._trigger( "open", event );
+	},
+
+	_position: function() {
+		this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) );
+	},
+
+	close: function( event ) {
+		if ( !this.isOpen ) {
+			return;
+		}
+
+		this.isOpen = false;
+		this._toggleAttr();
+
+		this.range = null;
+		this._off( this.document );
+
+		this._trigger( "close", event );
+	},
+
+	widget: function() {
+		return this.button;
+	},
+
+	menuWidget: function() {
+		return this.menu;
+	},
+
+	_renderMenu: function( ul, items ) {
+		var that = this,
+			currentOptgroup = "";
+
+		$.each( items, function( index, item ) {
+			if ( item.optgroup !== currentOptgroup ) {
+				$( "<li>", {
+					"class": "ui-selectmenu-optgroup ui-menu-divider" +
+						( item.element.parent( "optgroup" ).prop( "disabled" ) ?
+							" ui-state-disabled" :
+							"" ),
+					text: item.optgroup
+				})
+					.appendTo( ul );
+
+				currentOptgroup = item.optgroup;
+			}
+
+			that._renderItemData( ul, item );
+		});
+	},
+
+	_renderItemData: function( ul, item ) {
+		return this._renderItem( ul, item ).data( "ui-selectmenu-item", item );
+	},
+
+	_renderItem: function( ul, item ) {
+		var li = $( "<li>" );
+
+		if ( item.disabled ) {
+			li.addClass( "ui-state-disabled" );
+		}
+		this._setText( li, item.label );
+
+		return li.appendTo( ul );
+	},
+
+	_setText: function( element, value ) {
+		if ( value ) {
+			element.text( value );
+		} else {
+			element.html( "&#160;" );
+		}
+	},
+
+	_move: function( direction, event ) {
+		var item, next,
+			filter = ".ui-menu-item";
+
+		if ( this.isOpen ) {
+			item = this.menuItems.eq( this.focusIndex );
+		} else {
+			item = this.menuItems.eq( this.element[ 0 ].selectedIndex );
+			filter += ":not(.ui-state-disabled)";
+		}
+
+		if ( direction === "first" || direction === "last" ) {
+			next = item[ direction === "first" ? "prevAll" : "nextAll" ]( filter ).eq( -1 );
+		} else {
+			next = item[ direction + "All" ]( filter ).eq( 0 );
+		}
+
+		if ( next.length ) {
+			this.menuInstance.focus( event, next );
+		}
+	},
+
+	_getSelectedItem: function() {
+		return this.menuItems.eq( this.element[ 0 ].selectedIndex );
+	},
+
+	_toggle: function( event ) {
+		this[ this.isOpen ? "close" : "open" ]( event );
+	},
+
+	_setSelection: function() {
+		var selection;
+
+		if ( !this.range ) {
+			return;
+		}
+
+		if ( window.getSelection ) {
+			selection = window.getSelection();
+			selection.removeAllRanges();
+			selection.addRange( this.range );
+
+		// support: IE8
+		} else {
+			this.range.select();
+		}
+
+		// support: IE
+		// Setting the text selection kills the button focus in IE, but
+		// restoring the focus doesn't kill the selection.
+		this.button.focus();
+	},
+
+	_documentClick: {
+		mousedown: function( event ) {
+			if ( !this.isOpen ) {
+				return;
+			}
+
+			if ( !$( event.target ).closest( ".ui-selectmenu-menu, #" + this.ids.button ).length ) {
+				this.close( event );
+			}
+		}
+	},
+
+	_buttonEvents: {
+
+		// Prevent text selection from being reset when interacting with the selectmenu (#10144)
+		mousedown: function() {
+			var selection;
+
+			if ( window.getSelection ) {
+				selection = window.getSelection();
+				if ( selection.rangeCount ) {
+					this.range = selection.getRangeAt( 0 );
+				}
+
+			// support: IE8
+			} else {
+				this.range = document.selection.createRange();
+			}
+		},
+
+		click: function( event ) {
+			this._setSelection();
+			this._toggle( event );
+		},
+
+		keydown: function( event ) {
+			var preventDefault = true;
+			switch ( event.keyCode ) {
+				case $.ui.keyCode.TAB:
+				case $.ui.keyCode.ESCAPE:
+					this.close( event );
+					preventDefault = false;
+					break;
+				case $.ui.keyCode.ENTER:
+					if ( this.isOpen ) {
+						this._selectFocusedItem( event );
+					}
+					break;
+				case $.ui.keyCode.UP:
+					if ( event.altKey ) {
+						this._toggle( event );
+					} else {
+						this._move( "prev", event );
+					}
+					break;
+				case $.ui.keyCode.DOWN:
+					if ( event.altKey ) {
+						this._toggle( event );
+					} else {
+						this._move( "next", event );
+					}
+					break;
+				case $.ui.keyCode.SPACE:
+					if ( this.isOpen ) {
+						this._selectFocusedItem( event );
+					} else {
+						this._toggle( event );
+					}
+					break;
+				case $.ui.keyCode.LEFT:
+					this._move( "prev", event );
+					break;
+				case $.ui.keyCode.RIGHT:
+					this._move( "next", event );
+					break;
+				case $.ui.keyCode.HOME:
+				case $.ui.keyCode.PAGE_UP:
+					this._move( "first", event );
+					break;
+				case $.ui.keyCode.END:
+				case $.ui.keyCode.PAGE_DOWN:
+					this._move( "last", event );
+					break;
+				default:
+					this.menu.trigger( event );
+					preventDefault = false;
+			}
+
+			if ( preventDefault ) {
+				event.preventDefault();
+			}
+		}
+	},
+
+	_selectFocusedItem: function( event ) {
+		var item = this.menuItems.eq( this.focusIndex );
+		if ( !item.hasClass( "ui-state-disabled" ) ) {
+			this._select( item.data( "ui-selectmenu-item" ), event );
+		}
+	},
+
+	_select: function( item, event ) {
+		var oldIndex = this.element[ 0 ].selectedIndex;
+
+		// Change native select element
+		this.element[ 0 ].selectedIndex = item.index;
+		this._setText( this.buttonText, item.label );
+		this._setAria( item );
+		this._trigger( "select", event, { item: item } );
+
+		if ( item.index !== oldIndex ) {
+			this._trigger( "change", event, { item: item } );
+		}
+
+		this.close( event );
+	},
+
+	_setAria: function( item ) {
+		var id = this.menuItems.eq( item.index ).attr( "id" );
+
+		this.button.attr({
+			"aria-labelledby": id,
+			"aria-activedescendant": id
+		});
+		this.menu.attr( "aria-activedescendant", id );
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "icons" ) {
+			this.button.find( "span.ui-icon" )
+				.removeClass( this.options.icons.button )
+				.addClass( value.button );
+		}
+
+		this._super( key, value );
+
+		if ( key === "appendTo" ) {
+			this.menuWrap.appendTo( this._appendTo() );
+		}
+
+		if ( key === "disabled" ) {
+			this.menuInstance.option( "disabled", value );
+			this.button
+				.toggleClass( "ui-state-disabled", value )
+				.attr( "aria-disabled", value );
+
+			this.element.prop( "disabled", value );
+			if ( value ) {
+				this.button.attr( "tabindex", -1 );
+				this.close();
+			} else {
+				this.button.attr( "tabindex", 0 );
+			}
+		}
+
+		if ( key === "width" ) {
+			this._resizeButton();
+		}
+	},
+
+	_appendTo: function() {
+		var element = this.options.appendTo;
+
+		if ( element ) {
+			element = element.jquery || element.nodeType ?
+				$( element ) :
+				this.document.find( element ).eq( 0 );
+		}
+
+		if ( !element || !element[ 0 ] ) {
+			element = this.element.closest( ".ui-front" );
+		}
+
+		if ( !element.length ) {
+			element = this.document[ 0 ].body;
+		}
+
+		return element;
+	},
+
+	_toggleAttr: function() {
+		this.button
+			.toggleClass( "ui-corner-top", this.isOpen )
+			.toggleClass( "ui-corner-all", !this.isOpen )
+			.attr( "aria-expanded", this.isOpen );
+		this.menuWrap.toggleClass( "ui-selectmenu-open", this.isOpen );
+		this.menu.attr( "aria-hidden", !this.isOpen );
+	},
+
+	_resizeButton: function() {
+		var width = this.options.width;
+
+		if ( !width ) {
+			width = this.element.show().outerWidth();
+			this.element.hide();
+		}
+
+		this.button.outerWidth( width );
+	},
+
+	_resizeMenu: function() {
+		this.menu.outerWidth( Math.max(
+			this.button.outerWidth(),
+
+			// support: IE10
+			// IE10 wraps long text (possibly a rounding bug)
+			// so we add 1px to avoid the wrapping
+			this.menu.width( "" ).outerWidth() + 1
+		) );
+	},
+
+	_getCreateOptions: function() {
+		return { disabled: this.element.prop( "disabled" ) };
+	},
+
+	_parseOptions: function( options ) {
+		var data = [];
+		options.each(function( index, item ) {
+			var option = $( item ),
+				optgroup = option.parent( "optgroup" );
+			data.push({
+				element: option,
+				index: index,
+				value: option.val(),
+				label: option.text(),
+				optgroup: optgroup.attr( "label" ) || "",
+				disabled: optgroup.prop( "disabled" ) || option.prop( "disabled" )
+			});
+		});
+		this.items = data;
+	},
+
+	_destroy: function() {
+		this.menuWrap.remove();
+		this.button.remove();
+		this.element.show();
+		this.element.removeUniqueId();
+		this.label.attr( "for", this.ids.element );
+	}
+});
+
+
+/*!
+ * jQuery UI Slider 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/slider/
+ */
+
+
+var slider = $.widget( "ui.slider", $.ui.mouse, {
+	version: "1.11.4",
+	widgetEventPrefix: "slide",
+
+	options: {
+		animate: false,
+		distance: 0,
+		max: 100,
+		min: 0,
+		orientation: "horizontal",
+		range: false,
+		step: 1,
+		value: 0,
+		values: null,
+
+		// callbacks
+		change: null,
+		slide: null,
+		start: null,
+		stop: null
+	},
+
+	// number of pages in a slider
+	// (how many times can you page up/down to go through the whole range)
+	numPages: 5,
+
+	_create: function() {
+		this._keySliding = false;
+		this._mouseSliding = false;
+		this._animateOff = true;
+		this._handleIndex = null;
+		this._detectOrientation();
+		this._mouseInit();
+		this._calculateNewMax();
+
+		this.element
+			.addClass( "ui-slider" +
+				" ui-slider-" + this.orientation +
+				" ui-widget" +
+				" ui-widget-content" +
+				" ui-corner-all");
+
+		this._refresh();
+		this._setOption( "disabled", this.options.disabled );
+
+		this._animateOff = false;
+	},
+
+	_refresh: function() {
+		this._createRange();
+		this._createHandles();
+		this._setupEvents();
+		this._refreshValue();
+	},
+
+	_createHandles: function() {
+		var i, handleCount,
+			options = this.options,
+			existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
+			handle = "<span class='ui-slider-handle ui-state-default ui-corner-all' tabindex='0'></span>",
+			handles = [];
+
+		handleCount = ( options.values && options.values.length ) || 1;
+
+		if ( existingHandles.length > handleCount ) {
+			existingHandles.slice( handleCount ).remove();
+			existingHandles = existingHandles.slice( 0, handleCount );
+		}
+
+		for ( i = existingHandles.length; i < handleCount; i++ ) {
+			handles.push( handle );
+		}
+
+		this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
+
+		this.handle = this.handles.eq( 0 );
+
+		this.handles.each(function( i ) {
+			$( this ).data( "ui-slider-handle-index", i );
+		});
+	},
+
+	_createRange: function() {
+		var options = this.options,
+			classes = "";
+
+		if ( options.range ) {
+			if ( options.range === true ) {
+				if ( !options.values ) {
+					options.values = [ this._valueMin(), this._valueMin() ];
+				} else if ( options.values.length && options.values.length !== 2 ) {
+					options.values = [ options.values[0], options.values[0] ];
+				} else if ( $.isArray( options.values ) ) {
+					options.values = options.values.slice(0);
+				}
+			}
+
+			if ( !this.range || !this.range.length ) {
+				this.range = $( "<div></div>" )
+					.appendTo( this.element );
+
+				classes = "ui-slider-range" +
+				// note: this isn't the most fittingly semantic framework class for this element,
+				// but worked best visually with a variety of themes
+				" ui-widget-header ui-corner-all";
+			} else {
+				this.range.removeClass( "ui-slider-range-min ui-slider-range-max" )
+					// Handle range switching from true to min/max
+					.css({
+						"left": "",
+						"bottom": ""
+					});
+			}
+
+			this.range.addClass( classes +
+				( ( options.range === "min" || options.range === "max" ) ? " ui-slider-range-" + options.range : "" ) );
+		} else {
+			if ( this.range ) {
+				this.range.remove();
+			}
+			this.range = null;
+		}
+	},
+
+	_setupEvents: function() {
+		this._off( this.handles );
+		this._on( this.handles, this._handleEvents );
+		this._hoverable( this.handles );
+		this._focusable( this.handles );
+	},
+
+	_destroy: function() {
+		this.handles.remove();
+		if ( this.range ) {
+			this.range.remove();
+		}
+
+		this.element
+			.removeClass( "ui-slider" +
+				" ui-slider-horizontal" +
+				" ui-slider-vertical" +
+				" ui-widget" +
+				" ui-widget-content" +
+				" ui-corner-all" );
+
+		this._mouseDestroy();
+	},
+
+	_mouseCapture: function( event ) {
+		var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
+			that = this,
+			o = this.options;
+
+		if ( o.disabled ) {
+			return false;
+		}
+
+		this.elementSize = {
+			width: this.element.outerWidth(),
+			height: this.element.outerHeight()
+		};
+		this.elementOffset = this.element.offset();
+
+		position = { x: event.pageX, y: event.pageY };
+		normValue = this._normValueFromMouse( position );
+		distance = this._valueMax() - this._valueMin() + 1;
+		this.handles.each(function( i ) {
+			var thisDistance = Math.abs( normValue - that.values(i) );
+			if (( distance > thisDistance ) ||
+				( distance === thisDistance &&
+					(i === that._lastChangedValue || that.values(i) === o.min ))) {
+				distance = thisDistance;
+				closestHandle = $( this );
+				index = i;
+			}
+		});
+
+		allowed = this._start( event, index );
+		if ( allowed === false ) {
+			return false;
+		}
+		this._mouseSliding = true;
+
+		this._handleIndex = index;
+
+		closestHandle
+			.addClass( "ui-state-active" )
+			.focus();
+
+		offset = closestHandle.offset();
+		mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" );
+		this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
+			left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
+			top: event.pageY - offset.top -
+				( closestHandle.height() / 2 ) -
+				( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
+				( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
+				( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
+		};
+
+		if ( !this.handles.hasClass( "ui-state-hover" ) ) {
+			this._slide( event, index, normValue );
+		}
+		this._animateOff = true;
+		return true;
+	},
+
+	_mouseStart: function() {
+		return true;
+	},
+
+	_mouseDrag: function( event ) {
+		var position = { x: event.pageX, y: event.pageY },
+			normValue = this._normValueFromMouse( position );
+
+		this._slide( event, this._handleIndex, normValue );
+
+		return false;
+	},
+
+	_mouseStop: function( event ) {
+		this.handles.removeClass( "ui-state-active" );
+		this._mouseSliding = false;
+
+		this._stop( event, this._handleIndex );
+		this._change( event, this._handleIndex );
+
+		this._handleIndex = null;
+		this._clickOffset = null;
+		this._animateOff = false;
+
+		return false;
+	},
+
+	_detectOrientation: function() {
+		this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
+	},
+
+	_normValueFromMouse: function( position ) {
+		var pixelTotal,
+			pixelMouse,
+			percentMouse,
+			valueTotal,
+			valueMouse;
+
+		if ( this.orientation === "horizontal" ) {
+			pixelTotal = this.elementSize.width;
+			pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
+		} else {
+			pixelTotal = this.elementSize.height;
+			pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
+		}
+
+		percentMouse = ( pixelMouse / pixelTotal );
+		if ( percentMouse > 1 ) {
+			percentMouse = 1;
+		}
+		if ( percentMouse < 0 ) {
+			percentMouse = 0;
+		}
+		if ( this.orientation === "vertical" ) {
+			percentMouse = 1 - percentMouse;
+		}
+
+		valueTotal = this._valueMax() - this._valueMin();
+		valueMouse = this._valueMin() + percentMouse * valueTotal;
+
+		return this._trimAlignValue( valueMouse );
+	},
+
+	_start: function( event, index ) {
+		var uiHash = {
+			handle: this.handles[ index ],
+			value: this.value()
+		};
+		if ( this.options.values && this.options.values.length ) {
+			uiHash.value = this.values( index );
+			uiHash.values = this.values();
+		}
+		return this._trigger( "start", event, uiHash );
+	},
+
+	_slide: function( event, index, newVal ) {
+		var otherVal,
+			newValues,
+			allowed;
+
+		if ( this.options.values && this.options.values.length ) {
+			otherVal = this.values( index ? 0 : 1 );
+
+			if ( ( this.options.values.length === 2 && this.options.range === true ) &&
+					( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
+				) {
+				newVal = otherVal;
+			}
+
+			if ( newVal !== this.values( index ) ) {
+				newValues = this.values();
+				newValues[ index ] = newVal;
+				// A slide can be canceled by returning false from the slide callback
+				allowed = this._trigger( "slide", event, {
+					handle: this.handles[ index ],
+					value: newVal,
+					values: newValues
+				} );
+				otherVal = this.values( index ? 0 : 1 );
+				if ( allowed !== false ) {
+					this.values( index, newVal );
+				}
+			}
+		} else {
+			if ( newVal !== this.value() ) {
+				// A slide can be canceled by returning false from the slide callback
+				allowed = this._trigger( "slide", event, {
+					handle: this.handles[ index ],
+					value: newVal
+				} );
+				if ( allowed !== false ) {
+					this.value( newVal );
+				}
+			}
+		}
+	},
+
+	_stop: function( event, index ) {
+		var uiHash = {
+			handle: this.handles[ index ],
+			value: this.value()
+		};
+		if ( this.options.values && this.options.values.length ) {
+			uiHash.value = this.values( index );
+			uiHash.values = this.values();
+		}
+
+		this._trigger( "stop", event, uiHash );
+	},
+
+	_change: function( event, index ) {
+		if ( !this._keySliding && !this._mouseSliding ) {
+			var uiHash = {
+				handle: this.handles[ index ],
+				value: this.value()
+			};
+			if ( this.options.values && this.options.values.length ) {
+				uiHash.value = this.values( index );
+				uiHash.values = this.values();
+			}
+
+			//store the last changed value index for reference when handles overlap
+			this._lastChangedValue = index;
+
+			this._trigger( "change", event, uiHash );
+		}
+	},
+
+	value: function( newValue ) {
+		if ( arguments.length ) {
+			this.options.value = this._trimAlignValue( newValue );
+			this._refreshValue();
+			this._change( null, 0 );
+			return;
+		}
+
+		return this._value();
+	},
+
+	values: function( index, newValue ) {
+		var vals,
+			newValues,
+			i;
+
+		if ( arguments.length > 1 ) {
+			this.options.values[ index ] = this._trimAlignValue( newValue );
+			this._refreshValue();
+			this._change( null, index );
+			return;
+		}
+
+		if ( arguments.length ) {
+			if ( $.isArray( arguments[ 0 ] ) ) {
+				vals = this.options.values;
+				newValues = arguments[ 0 ];
+				for ( i = 0; i < vals.length; i += 1 ) {
+					vals[ i ] = this._trimAlignValue( newValues[ i ] );
+					this._change( null, i );
+				}
+				this._refreshValue();
+			} else {
+				if ( this.options.values && this.options.values.length ) {
+					return this._values( index );
+				} else {
+					return this.value();
+				}
+			}
+		} else {
+			return this._values();
+		}
+	},
+
+	_setOption: function( key, value ) {
+		var i,
+			valsLength = 0;
+
+		if ( key === "range" && this.options.range === true ) {
+			if ( value === "min" ) {
+				this.options.value = this._values( 0 );
+				this.options.values = null;
+			} else if ( value === "max" ) {
+				this.options.value = this._values( this.options.values.length - 1 );
+				this.options.values = null;
+			}
+		}
+
+		if ( $.isArray( this.options.values ) ) {
+			valsLength = this.options.values.length;
+		}
+
+		if ( key === "disabled" ) {
+			this.element.toggleClass( "ui-state-disabled", !!value );
+		}
+
+		this._super( key, value );
+
+		switch ( key ) {
+			case "orientation":
+				this._detectOrientation();
+				this.element
+					.removeClass( "ui-slider-horizontal ui-slider-vertical" )
+					.addClass( "ui-slider-" + this.orientation );
+				this._refreshValue();
+
+				// Reset positioning from previous orientation
+				this.handles.css( value === "horizontal" ? "bottom" : "left", "" );
+				break;
+			case "value":
+				this._animateOff = true;
+				this._refreshValue();
+				this._change( null, 0 );
+				this._animateOff = false;
+				break;
+			case "values":
+				this._animateOff = true;
+				this._refreshValue();
+				for ( i = 0; i < valsLength; i += 1 ) {
+					this._change( null, i );
+				}
+				this._animateOff = false;
+				break;
+			case "step":
+			case "min":
+			case "max":
+				this._animateOff = true;
+				this._calculateNewMax();
+				this._refreshValue();
+				this._animateOff = false;
+				break;
+			case "range":
+				this._animateOff = true;
+				this._refresh();
+				this._animateOff = false;
+				break;
+		}
+	},
+
+	//internal value getter
+	// _value() returns value trimmed by min and max, aligned by step
+	_value: function() {
+		var val = this.options.value;
+		val = this._trimAlignValue( val );
+
+		return val;
+	},
+
+	//internal values getter
+	// _values() returns array of values trimmed by min and max, aligned by step
+	// _values( index ) returns single value trimmed by min and max, aligned by step
+	_values: function( index ) {
+		var val,
+			vals,
+			i;
+
+		if ( arguments.length ) {
+			val = this.options.values[ index ];
+			val = this._trimAlignValue( val );
+
+			return val;
+		} else if ( this.options.values && this.options.values.length ) {
+			// .slice() creates a copy of the array
+			// this copy gets trimmed by min and max and then returned
+			vals = this.options.values.slice();
+			for ( i = 0; i < vals.length; i += 1) {
+				vals[ i ] = this._trimAlignValue( vals[ i ] );
+			}
+
+			return vals;
+		} else {
+			return [];
+		}
+	},
+
+	// returns the step-aligned value that val is closest to, between (inclusive) min and max
+	_trimAlignValue: function( val ) {
+		if ( val <= this._valueMin() ) {
+			return this._valueMin();
+		}
+		if ( val >= this._valueMax() ) {
+			return this._valueMax();
+		}
+		var step = ( this.options.step > 0 ) ? this.options.step : 1,
+			valModStep = (val - this._valueMin()) % step,
+			alignValue = val - valModStep;
+
+		if ( Math.abs(valModStep) * 2 >= step ) {
+			alignValue += ( valModStep > 0 ) ? step : ( -step );
+		}
+
+		// Since JavaScript has problems with large floats, round
+		// the final value to 5 digits after the decimal point (see #4124)
+		return parseFloat( alignValue.toFixed(5) );
+	},
+
+	_calculateNewMax: function() {
+		var max = this.options.max,
+			min = this._valueMin(),
+			step = this.options.step,
+			aboveMin = Math.floor( ( +( max - min ).toFixed( this._precision() ) ) / step ) * step;
+		max = aboveMin + min;
+		this.max = parseFloat( max.toFixed( this._precision() ) );
+	},
+
+	_precision: function() {
+		var precision = this._precisionOf( this.options.step );
+		if ( this.options.min !== null ) {
+			precision = Math.max( precision, this._precisionOf( this.options.min ) );
+		}
+		return precision;
+	},
+
+	_precisionOf: function( num ) {
+		var str = num.toString(),
+			decimal = str.indexOf( "." );
+		return decimal === -1 ? 0 : str.length - decimal - 1;
+	},
+
+	_valueMin: function() {
+		return this.options.min;
+	},
+
+	_valueMax: function() {
+		return this.max;
+	},
+
+	_refreshValue: function() {
+		var lastValPercent, valPercent, value, valueMin, valueMax,
+			oRange = this.options.range,
+			o = this.options,
+			that = this,
+			animate = ( !this._animateOff ) ? o.animate : false,
+			_set = {};
+
+		if ( this.options.values && this.options.values.length ) {
+			this.handles.each(function( i ) {
+				valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
+				_set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+				$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+				if ( that.options.range === true ) {
+					if ( that.orientation === "horizontal" ) {
+						if ( i === 0 ) {
+							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
+						}
+						if ( i === 1 ) {
+							that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+						}
+					} else {
+						if ( i === 0 ) {
+							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
+						}
+						if ( i === 1 ) {
+							that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+						}
+					}
+				}
+				lastValPercent = valPercent;
+			});
+		} else {
+			value = this.value();
+			valueMin = this._valueMin();
+			valueMax = this._valueMax();
+			valPercent = ( valueMax !== valueMin ) ?
+					( value - valueMin ) / ( valueMax - valueMin ) * 100 :
+					0;
+			_set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+			this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+
+			if ( oRange === "min" && this.orientation === "horizontal" ) {
+				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
+			}
+			if ( oRange === "max" && this.orientation === "horizontal" ) {
+				this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+			}
+			if ( oRange === "min" && this.orientation === "vertical" ) {
+				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
+			}
+			if ( oRange === "max" && this.orientation === "vertical" ) {
+				this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+			}
+		}
+	},
+
+	_handleEvents: {
+		keydown: function( event ) {
+			var allowed, curVal, newVal, step,
+				index = $( event.target ).data( "ui-slider-handle-index" );
+
+			switch ( event.keyCode ) {
+				case $.ui.keyCode.HOME:
+				case $.ui.keyCode.END:
+				case $.ui.keyCode.PAGE_UP:
+				case $.ui.keyCode.PAGE_DOWN:
+				case $.ui.keyCode.UP:
+				case $.ui.keyCode.RIGHT:
+				case $.ui.keyCode.DOWN:
+				case $.ui.keyCode.LEFT:
+					event.preventDefault();
+					if ( !this._keySliding ) {
+						this._keySliding = true;
+						$( event.target ).addClass( "ui-state-active" );
+						allowed = this._start( event, index );
+						if ( allowed === false ) {
+							return;
+						}
+					}
+					break;
+			}
+
+			step = this.options.step;
+			if ( this.options.values && this.options.values.length ) {
+				curVal = newVal = this.values( index );
+			} else {
+				curVal = newVal = this.value();
+			}
+
+			switch ( event.keyCode ) {
+				case $.ui.keyCode.HOME:
+					newVal = this._valueMin();
+					break;
+				case $.ui.keyCode.END:
+					newVal = this._valueMax();
+					break;
+				case $.ui.keyCode.PAGE_UP:
+					newVal = this._trimAlignValue(
+						curVal + ( ( this._valueMax() - this._valueMin() ) / this.numPages )
+					);
+					break;
+				case $.ui.keyCode.PAGE_DOWN:
+					newVal = this._trimAlignValue(
+						curVal - ( (this._valueMax() - this._valueMin()) / this.numPages ) );
+					break;
+				case $.ui.keyCode.UP:
+				case $.ui.keyCode.RIGHT:
+					if ( curVal === this._valueMax() ) {
+						return;
+					}
+					newVal = this._trimAlignValue( curVal + step );
+					break;
+				case $.ui.keyCode.DOWN:
+				case $.ui.keyCode.LEFT:
+					if ( curVal === this._valueMin() ) {
+						return;
+					}
+					newVal = this._trimAlignValue( curVal - step );
+					break;
+			}
+
+			this._slide( event, index, newVal );
+		},
+		keyup: function( event ) {
+			var index = $( event.target ).data( "ui-slider-handle-index" );
+
+			if ( this._keySliding ) {
+				this._keySliding = false;
+				this._stop( event, index );
+				this._change( event, index );
+				$( event.target ).removeClass( "ui-state-active" );
+			}
+		}
+	}
+});
+
+
+/*!
+ * jQuery UI Spinner 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/spinner/
+ */
+
+
+function spinner_modifier( fn ) {
+	return function() {
+		var previous = this.element.val();
+		fn.apply( this, arguments );
+		this._refresh();
+		if ( previous !== this.element.val() ) {
+			this._trigger( "change" );
+		}
+	};
+}
+
+var spinner = $.widget( "ui.spinner", {
+	version: "1.11.4",
+	defaultElement: "<input>",
+	widgetEventPrefix: "spin",
+	options: {
+		culture: null,
+		icons: {
+			down: "ui-icon-triangle-1-s",
+			up: "ui-icon-triangle-1-n"
+		},
+		incremental: true,
+		max: null,
+		min: null,
+		numberFormat: null,
+		page: 10,
+		step: 1,
+
+		change: null,
+		spin: null,
+		start: null,
+		stop: null
+	},
+
+	_create: function() {
+		// handle string values that need to be parsed
+		this._setOption( "max", this.options.max );
+		this._setOption( "min", this.options.min );
+		this._setOption( "step", this.options.step );
+
+		// Only format if there is a value, prevents the field from being marked
+		// as invalid in Firefox, see #9573.
+		if ( this.value() !== "" ) {
+			// Format the value, but don't constrain.
+			this._value( this.element.val(), true );
+		}
+
+		this._draw();
+		this._on( this._events );
+		this._refresh();
+
+		// turning off autocomplete prevents the browser from remembering the
+		// value when navigating through history, so we re-enable autocomplete
+		// if the page is unloaded before the widget is destroyed. #7790
+		this._on( this.window, {
+			beforeunload: function() {
+				this.element.removeAttr( "autocomplete" );
+			}
+		});
+	},
+
+	_getCreateOptions: function() {
+		var options = {},
+			element = this.element;
+
+		$.each( [ "min", "max", "step" ], function( i, option ) {
+			var value = element.attr( option );
+			if ( value !== undefined && value.length ) {
+				options[ option ] = value;
+			}
+		});
+
+		return options;
+	},
+
+	_events: {
+		keydown: function( event ) {
+			if ( this._start( event ) && this._keydown( event ) ) {
+				event.preventDefault();
+			}
+		},
+		keyup: "_stop",
+		focus: function() {
+			this.previous = this.element.val();
+		},
+		blur: function( event ) {
+			if ( this.cancelBlur ) {
+				delete this.cancelBlur;
+				return;
+			}
+
+			this._stop();
+			this._refresh();
+			if ( this.previous !== this.element.val() ) {
+				this._trigger( "change", event );
+			}
+		},
+		mousewheel: function( event, delta ) {
+			if ( !delta ) {
+				return;
+			}
+			if ( !this.spinning && !this._start( event ) ) {
+				return false;
+			}
+
+			this._spin( (delta > 0 ? 1 : -1) * this.options.step, event );
+			clearTimeout( this.mousewheelTimer );
+			this.mousewheelTimer = this._delay(function() {
+				if ( this.spinning ) {
+					this._stop( event );
+				}
+			}, 100 );
+			event.preventDefault();
+		},
+		"mousedown .ui-spinner-button": function( event ) {
+			var previous;
+
+			// We never want the buttons to have focus; whenever the user is
+			// interacting with the spinner, the focus should be on the input.
+			// If the input is focused then this.previous is properly set from
+			// when the input first received focus. If the input is not focused
+			// then we need to set this.previous based on the value before spinning.
+			previous = this.element[0] === this.document[0].activeElement ?
+				this.previous : this.element.val();
+			function checkFocus() {
+				var isActive = this.element[0] === this.document[0].activeElement;
+				if ( !isActive ) {
+					this.element.focus();
+					this.previous = previous;
+					// support: IE
+					// IE sets focus asynchronously, so we need to check if focus
+					// moved off of the input because the user clicked on the button.
+					this._delay(function() {
+						this.previous = previous;
+					});
+				}
+			}
+
+			// ensure focus is on (or stays on) the text field
+			event.preventDefault();
+			checkFocus.call( this );
+
+			// support: IE
+			// IE doesn't prevent moving focus even with event.preventDefault()
+			// so we set a flag to know when we should ignore the blur event
+			// and check (again) if focus moved off of the input.
+			this.cancelBlur = true;
+			this._delay(function() {
+				delete this.cancelBlur;
+				checkFocus.call( this );
+			});
+
+			if ( this._start( event ) === false ) {
+				return;
+			}
+
+			this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
+		},
+		"mouseup .ui-spinner-button": "_stop",
+		"mouseenter .ui-spinner-button": function( event ) {
+			// button will add ui-state-active if mouse was down while mouseleave and kept down
+			if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
+				return;
+			}
+
+			if ( this._start( event ) === false ) {
+				return false;
+			}
+			this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
+		},
+		// TODO: do we really want to consider this a stop?
+		// shouldn't we just stop the repeater and wait until mouseup before
+		// we trigger the stop event?
+		"mouseleave .ui-spinner-button": "_stop"
+	},
+
+	_draw: function() {
+		var uiSpinner = this.uiSpinner = this.element
+			.addClass( "ui-spinner-input" )
+			.attr( "autocomplete", "off" )
+			.wrap( this._uiSpinnerHtml() )
+			.parent()
+				// add buttons
+				.append( this._buttonHtml() );
+
+		this.element.attr( "role", "spinbutton" );
+
+		// button bindings
+		this.buttons = uiSpinner.find( ".ui-spinner-button" )
+			.attr( "tabIndex", -1 )
+			.button()
+			.removeClass( "ui-corner-all" );
+
+		// IE 6 doesn't understand height: 50% for the buttons
+		// unless the wrapper has an explicit height
+		if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) &&
+				uiSpinner.height() > 0 ) {
+			uiSpinner.height( uiSpinner.height() );
+		}
+
+		// disable spinner if element was already disabled
+		if ( this.options.disabled ) {
+			this.disable();
+		}
+	},
+
+	_keydown: function( event ) {
+		var options = this.options,
+			keyCode = $.ui.keyCode;
+
+		switch ( event.keyCode ) {
+		case keyCode.UP:
+			this._repeat( null, 1, event );
+			return true;
+		case keyCode.DOWN:
+			this._repeat( null, -1, event );
+			return true;
+		case keyCode.PAGE_UP:
+			this._repeat( null, options.page, event );
+			return true;
+		case keyCode.PAGE_DOWN:
+			this._repeat( null, -options.page, event );
+			return true;
+		}
+
+		return false;
+	},
+
+	_uiSpinnerHtml: function() {
+		return "<span class='ui-spinner ui-widget ui-widget-content ui-corner-all'></span>";
+	},
+
+	_buttonHtml: function() {
+		return "" +
+			"<a class='ui-spinner-button ui-spinner-up ui-corner-tr'>" +
+				"<span class='ui-icon " + this.options.icons.up + "'>&#9650;</span>" +
+			"</a>" +
+			"<a class='ui-spinner-button ui-spinner-down ui-corner-br'>" +
+				"<span class='ui-icon " + this.options.icons.down + "'>&#9660;</span>" +
+			"</a>";
+	},
+
+	_start: function( event ) {
+		if ( !this.spinning && this._trigger( "start", event ) === false ) {
+			return false;
+		}
+
+		if ( !this.counter ) {
+			this.counter = 1;
+		}
+		this.spinning = true;
+		return true;
+	},
+
+	_repeat: function( i, steps, event ) {
+		i = i || 500;
+
+		clearTimeout( this.timer );
+		this.timer = this._delay(function() {
+			this._repeat( 40, steps, event );
+		}, i );
+
+		this._spin( steps * this.options.step, event );
+	},
+
+	_spin: function( step, event ) {
+		var value = this.value() || 0;
+
+		if ( !this.counter ) {
+			this.counter = 1;
+		}
+
+		value = this._adjustValue( value + step * this._increment( this.counter ) );
+
+		if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) {
+			this._value( value );
+			this.counter++;
+		}
+	},
+
+	_increment: function( i ) {
+		var incremental = this.options.incremental;
+
+		if ( incremental ) {
+			return $.isFunction( incremental ) ?
+				incremental( i ) :
+				Math.floor( i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1 );
+		}
+
+		return 1;
+	},
+
+	_precision: function() {
+		var precision = this._precisionOf( this.options.step );
+		if ( this.options.min !== null ) {
+			precision = Math.max( precision, this._precisionOf( this.options.min ) );
+		}
+		return precision;
+	},
+
+	_precisionOf: function( num ) {
+		var str = num.toString(),
+			decimal = str.indexOf( "." );
+		return decimal === -1 ? 0 : str.length - decimal - 1;
+	},
+
+	_adjustValue: function( value ) {
+		var base, aboveMin,
+			options = this.options;
+
+		// make sure we're at a valid step
+		// - find out where we are relative to the base (min or 0)
+		base = options.min !== null ? options.min : 0;
+		aboveMin = value - base;
+		// - round to the nearest step
+		aboveMin = Math.round(aboveMin / options.step) * options.step;
+		// - rounding is based on 0, so adjust back to our base
+		value = base + aboveMin;
+
+		// fix precision from bad JS floating point math
+		value = parseFloat( value.toFixed( this._precision() ) );
+
+		// clamp the value
+		if ( options.max !== null && value > options.max) {
+			return options.max;
+		}
+		if ( options.min !== null && value < options.min ) {
+			return options.min;
+		}
+
+		return value;
+	},
+
+	_stop: function( event ) {
+		if ( !this.spinning ) {
+			return;
+		}
+
+		clearTimeout( this.timer );
+		clearTimeout( this.mousewheelTimer );
+		this.counter = 0;
+		this.spinning = false;
+		this._trigger( "stop", event );
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "culture" || key === "numberFormat" ) {
+			var prevValue = this._parse( this.element.val() );
+			this.options[ key ] = value;
+			this.element.val( this._format( prevValue ) );
+			return;
+		}
+
+		if ( key === "max" || key === "min" || key === "step" ) {
+			if ( typeof value === "string" ) {
+				value = this._parse( value );
+			}
+		}
+		if ( key === "icons" ) {
+			this.buttons.first().find( ".ui-icon" )
+				.removeClass( this.options.icons.up )
+				.addClass( value.up );
+			this.buttons.last().find( ".ui-icon" )
+				.removeClass( this.options.icons.down )
+				.addClass( value.down );
+		}
+
+		this._super( key, value );
+
+		if ( key === "disabled" ) {
+			this.widget().toggleClass( "ui-state-disabled", !!value );
+			this.element.prop( "disabled", !!value );
+			this.buttons.button( value ? "disable" : "enable" );
+		}
+	},
+
+	_setOptions: spinner_modifier(function( options ) {
+		this._super( options );
+	}),
+
+	_parse: function( val ) {
+		if ( typeof val === "string" && val !== "" ) {
+			val = window.Globalize && this.options.numberFormat ?
+				Globalize.parseFloat( val, 10, this.options.culture ) : +val;
+		}
+		return val === "" || isNaN( val ) ? null : val;
+	},
+
+	_format: function( value ) {
+		if ( value === "" ) {
+			return "";
+		}
+		return window.Globalize && this.options.numberFormat ?
+			Globalize.format( value, this.options.numberFormat, this.options.culture ) :
+			value;
+	},
+
+	_refresh: function() {
+		this.element.attr({
+			"aria-valuemin": this.options.min,
+			"aria-valuemax": this.options.max,
+			// TODO: what should we do with values that can't be parsed?
+			"aria-valuenow": this._parse( this.element.val() )
+		});
+	},
+
+	isValid: function() {
+		var value = this.value();
+
+		// null is invalid
+		if ( value === null ) {
+			return false;
+		}
+
+		// if value gets adjusted, it's invalid
+		return value === this._adjustValue( value );
+	},
+
+	// update the value without triggering change
+	_value: function( value, allowAny ) {
+		var parsed;
+		if ( value !== "" ) {
+			parsed = this._parse( value );
+			if ( parsed !== null ) {
+				if ( !allowAny ) {
+					parsed = this._adjustValue( parsed );
+				}
+				value = this._format( parsed );
+			}
+		}
+		this.element.val( value );
+		this._refresh();
+	},
+
+	_destroy: function() {
+		this.element
+			.removeClass( "ui-spinner-input" )
+			.prop( "disabled", false )
+			.removeAttr( "autocomplete" )
+			.removeAttr( "role" )
+			.removeAttr( "aria-valuemin" )
+			.removeAttr( "aria-valuemax" )
+			.removeAttr( "aria-valuenow" );
+		this.uiSpinner.replaceWith( this.element );
+	},
+
+	stepUp: spinner_modifier(function( steps ) {
+		this._stepUp( steps );
+	}),
+	_stepUp: function( steps ) {
+		if ( this._start() ) {
+			this._spin( (steps || 1) * this.options.step );
+			this._stop();
+		}
+	},
+
+	stepDown: spinner_modifier(function( steps ) {
+		this._stepDown( steps );
+	}),
+	_stepDown: function( steps ) {
+		if ( this._start() ) {
+			this._spin( (steps || 1) * -this.options.step );
+			this._stop();
+		}
+	},
+
+	pageUp: spinner_modifier(function( pages ) {
+		this._stepUp( (pages || 1) * this.options.page );
+	}),
+
+	pageDown: spinner_modifier(function( pages ) {
+		this._stepDown( (pages || 1) * this.options.page );
+	}),
+
+	value: function( newVal ) {
+		if ( !arguments.length ) {
+			return this._parse( this.element.val() );
+		}
+		spinner_modifier( this._value ).call( this, newVal );
+	},
+
+	widget: function() {
+		return this.uiSpinner;
+	}
+});
+
+
+/*!
+ * jQuery UI Tabs 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/tabs/
+ */
+
+
+var tabs = $.widget( "ui.tabs", {
+	version: "1.11.4",
+	delay: 300,
+	options: {
+		active: null,
+		collapsible: false,
+		event: "click",
+		heightStyle: "content",
+		hide: null,
+		show: null,
+
+		// callbacks
+		activate: null,
+		beforeActivate: null,
+		beforeLoad: null,
+		load: null
+	},
+
+	_isLocal: (function() {
+		var rhash = /#.*$/;
+
+		return function( anchor ) {
+			var anchorUrl, locationUrl;
+
+			// support: IE7
+			// IE7 doesn't normalize the href property when set via script (#9317)
+			anchor = anchor.cloneNode( false );
+
+			anchorUrl = anchor.href.replace( rhash, "" );
+			locationUrl = location.href.replace( rhash, "" );
+
+			// decoding may throw an error if the URL isn't UTF-8 (#9518)
+			try {
+				anchorUrl = decodeURIComponent( anchorUrl );
+			} catch ( error ) {}
+			try {
+				locationUrl = decodeURIComponent( locationUrl );
+			} catch ( error ) {}
+
+			return anchor.hash.length > 1 && anchorUrl === locationUrl;
+		};
+	})(),
+
+	_create: function() {
+		var that = this,
+			options = this.options;
+
+		this.running = false;
+
+		this.element
+			.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" )
+			.toggleClass( "ui-tabs-collapsible", options.collapsible );
+
+		this._processTabs();
+		options.active = this._initialActive();
+
+		// Take disabling tabs via class attribute from HTML
+		// into account and update option properly.
+		if ( $.isArray( options.disabled ) ) {
+			options.disabled = $.unique( options.disabled.concat(
+				$.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
+					return that.tabs.index( li );
+				})
+			) ).sort();
+		}
+
+		// check for length avoids error when initializing empty list
+		if ( this.options.active !== false && this.anchors.length ) {
+			this.active = this._findActive( options.active );
+		} else {
+			this.active = $();
+		}
+
+		this._refresh();
+
+		if ( this.active.length ) {
+			this.load( options.active );
+		}
+	},
+
+	_initialActive: function() {
+		var active = this.options.active,
+			collapsible = this.options.collapsible,
+			locationHash = location.hash.substring( 1 );
+
+		if ( active === null ) {
+			// check the fragment identifier in the URL
+			if ( locationHash ) {
+				this.tabs.each(function( i, tab ) {
+					if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
+						active = i;
+						return false;
+					}
+				});
+			}
+
+			// check for a tab marked active via a class
+			if ( active === null ) {
+				active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
+			}
+
+			// no active tab, set to false
+			if ( active === null || active === -1 ) {
+				active = this.tabs.length ? 0 : false;
+			}
+		}
+
+		// handle numbers: negative, out of range
+		if ( active !== false ) {
+			active = this.tabs.index( this.tabs.eq( active ) );
+			if ( active === -1 ) {
+				active = collapsible ? false : 0;
+			}
+		}
+
+		// don't allow collapsible: false and active: false
+		if ( !collapsible && active === false && this.anchors.length ) {
+			active = 0;
+		}
+
+		return active;
+	},
+
+	_getCreateEventData: function() {
+		return {
+			tab: this.active,
+			panel: !this.active.length ? $() : this._getPanelForTab( this.active )
+		};
+	},
+
+	_tabKeydown: function( event ) {
+		var focusedTab = $( this.document[0].activeElement ).closest( "li" ),
+			selectedIndex = this.tabs.index( focusedTab ),
+			goingForward = true;
+
+		if ( this._handlePageNav( event ) ) {
+			return;
+		}
+
+		switch ( event.keyCode ) {
+			case $.ui.keyCode.RIGHT:
+			case $.ui.keyCode.DOWN:
+				selectedIndex++;
+				break;
+			case $.ui.keyCode.UP:
+			case $.ui.keyCode.LEFT:
+				goingForward = false;
+				selectedIndex--;
+				break;
+			case $.ui.keyCode.END:
+				selectedIndex = this.anchors.length - 1;
+				break;
+			case $.ui.keyCode.HOME:
+				selectedIndex = 0;
+				break;
+			case $.ui.keyCode.SPACE:
+				// Activate only, no collapsing
+				event.preventDefault();
+				clearTimeout( this.activating );
+				this._activate( selectedIndex );
+				return;
+			case $.ui.keyCode.ENTER:
+				// Toggle (cancel delayed activation, allow collapsing)
+				event.preventDefault();
+				clearTimeout( this.activating );
+				// Determine if we should collapse or activate
+				this._activate( selectedIndex === this.options.active ? false : selectedIndex );
+				return;
+			default:
+				return;
+		}
+
+		// Focus the appropriate tab, based on which key was pressed
+		event.preventDefault();
+		clearTimeout( this.activating );
+		selectedIndex = this._focusNextTab( selectedIndex, goingForward );
+
+		// Navigating with control/command key will prevent automatic activation
+		if ( !event.ctrlKey && !event.metaKey ) {
+
+			// Update aria-selected immediately so that AT think the tab is already selected.
+			// Otherwise AT may confuse the user by stating that they need to activate the tab,
+			// but the tab will already be activated by the time the announcement finishes.
+			focusedTab.attr( "aria-selected", "false" );
+			this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );
+
+			this.activating = this._delay(function() {
+				this.option( "active", selectedIndex );
+			}, this.delay );
+		}
+	},
+
+	_panelKeydown: function( event ) {
+		if ( this._handlePageNav( event ) ) {
+			return;
+		}
+
+		// Ctrl+up moves focus to the current tab
+		if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
+			event.preventDefault();
+			this.active.focus();
+		}
+	},
+
+	// Alt+page up/down moves focus to the previous/next tab (and activates)
+	_handlePageNav: function( event ) {
+		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
+			this._activate( this._focusNextTab( this.options.active - 1, false ) );
+			return true;
+		}
+		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
+			this._activate( this._focusNextTab( this.options.active + 1, true ) );
+			return true;
+		}
+	},
+
+	_findNextTab: function( index, goingForward ) {
+		var lastTabIndex = this.tabs.length - 1;
+
+		function constrain() {
+			if ( index > lastTabIndex ) {
+				index = 0;
+			}
+			if ( index < 0 ) {
+				index = lastTabIndex;
+			}
+			return index;
+		}
+
+		while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
+			index = goingForward ? index + 1 : index - 1;
+		}
+
+		return index;
+	},
+
+	_focusNextTab: function( index, goingForward ) {
+		index = this._findNextTab( index, goingForward );
+		this.tabs.eq( index ).focus();
+		return index;
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "active" ) {
+			// _activate() will handle invalid values and update this.options
+			this._activate( value );
+			return;
+		}
+
+		if ( key === "disabled" ) {
+			// don't use the widget factory's disabled handling
+			this._setupDisabled( value );
+			return;
+		}
+
+		this._super( key, value);
+
+		if ( key === "collapsible" ) {
+			this.element.toggleClass( "ui-tabs-collapsible", value );
+			// Setting collapsible: false while collapsed; open first panel
+			if ( !value && this.options.active === false ) {
+				this._activate( 0 );
+			}
+		}
+
+		if ( key === "event" ) {
+			this._setupEvents( value );
+		}
+
+		if ( key === "heightStyle" ) {
+			this._setupHeightStyle( value );
+		}
+	},
+
+	_sanitizeSelector: function( hash ) {
+		return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
+	},
+
+	refresh: function() {
+		var options = this.options,
+			lis = this.tablist.children( ":has(a[href])" );
+
+		// get disabled tabs from class attribute from HTML
+		// this will get converted to a boolean if needed in _refresh()
+		options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
+			return lis.index( tab );
+		});
+
+		this._processTabs();
+
+		// was collapsed or no tabs
+		if ( options.active === false || !this.anchors.length ) {
+			options.active = false;
+			this.active = $();
+		// was active, but active tab is gone
+		} else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
+			// all remaining tabs are disabled
+			if ( this.tabs.length === options.disabled.length ) {
+				options.active = false;
+				this.active = $();
+			// activate previous tab
+			} else {
+				this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
+			}
+		// was active, active tab still exists
+		} else {
+			// make sure active index is correct
+			options.active = this.tabs.index( this.active );
+		}
+
+		this._refresh();
+	},
+
+	_refresh: function() {
+		this._setupDisabled( this.options.disabled );
+		this._setupEvents( this.options.event );
+		this._setupHeightStyle( this.options.heightStyle );
+
+		this.tabs.not( this.active ).attr({
+			"aria-selected": "false",
+			"aria-expanded": "false",
+			tabIndex: -1
+		});
+		this.panels.not( this._getPanelForTab( this.active ) )
+			.hide()
+			.attr({
+				"aria-hidden": "true"
+			});
+
+		// Make sure one tab is in the tab order
+		if ( !this.active.length ) {
+			this.tabs.eq( 0 ).attr( "tabIndex", 0 );
+		} else {
+			this.active
+				.addClass( "ui-tabs-active ui-state-active" )
+				.attr({
+					"aria-selected": "true",
+					"aria-expanded": "true",
+					tabIndex: 0
+				});
+			this._getPanelForTab( this.active )
+				.show()
+				.attr({
+					"aria-hidden": "false"
+				});
+		}
+	},
+
+	_processTabs: function() {
+		var that = this,
+			prevTabs = this.tabs,
+			prevAnchors = this.anchors,
+			prevPanels = this.panels;
+
+		this.tablist = this._getList()
+			.addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
+			.attr( "role", "tablist" )
+
+			// Prevent users from focusing disabled tabs via click
+			.delegate( "> li", "mousedown" + this.eventNamespace, function( event ) {
+				if ( $( this ).is( ".ui-state-disabled" ) ) {
+					event.preventDefault();
+				}
+			})
+
+			// support: IE <9
+			// Preventing the default action in mousedown doesn't prevent IE
+			// from focusing the element, so if the anchor gets focused, blur.
+			// We don't have to worry about focusing the previously focused
+			// element since clicking on a non-focusable element should focus
+			// the body anyway.
+			.delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() {
+				if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
+					this.blur();
+				}
+			});
+
+		this.tabs = this.tablist.find( "> li:has(a[href])" )
+			.addClass( "ui-state-default ui-corner-top" )
+			.attr({
+				role: "tab",
+				tabIndex: -1
+			});
+
+		this.anchors = this.tabs.map(function() {
+				return $( "a", this )[ 0 ];
+			})
+			.addClass( "ui-tabs-anchor" )
+			.attr({
+				role: "presentation",
+				tabIndex: -1
+			});
+
+		this.panels = $();
+
+		this.anchors.each(function( i, anchor ) {
+			var selector, panel, panelId,
+				anchorId = $( anchor ).uniqueId().attr( "id" ),
+				tab = $( anchor ).closest( "li" ),
+				originalAriaControls = tab.attr( "aria-controls" );
+
+			// inline tab
+			if ( that._isLocal( anchor ) ) {
+				selector = anchor.hash;
+				panelId = selector.substring( 1 );
+				panel = that.element.find( that._sanitizeSelector( selector ) );
+			// remote tab
+			} else {
+				// If the tab doesn't already have aria-controls,
+				// generate an id by using a throw-away element
+				panelId = tab.attr( "aria-controls" ) || $( {} ).uniqueId()[ 0 ].id;
+				selector = "#" + panelId;
+				panel = that.element.find( selector );
+				if ( !panel.length ) {
+					panel = that._createPanel( panelId );
+					panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
+				}
+				panel.attr( "aria-live", "polite" );
+			}
+
+			if ( panel.length) {
+				that.panels = that.panels.add( panel );
+			}
+			if ( originalAriaControls ) {
+				tab.data( "ui-tabs-aria-controls", originalAriaControls );
+			}
+			tab.attr({
+				"aria-controls": panelId,
+				"aria-labelledby": anchorId
+			});
+			panel.attr( "aria-labelledby", anchorId );
+		});
+
+		this.panels
+			.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
+			.attr( "role", "tabpanel" );
+
+		// Avoid memory leaks (#10056)
+		if ( prevTabs ) {
+			this._off( prevTabs.not( this.tabs ) );
+			this._off( prevAnchors.not( this.anchors ) );
+			this._off( prevPanels.not( this.panels ) );
+		}
+	},
+
+	// allow overriding how to find the list for rare usage scenarios (#7715)
+	_getList: function() {
+		return this.tablist || this.element.find( "ol,ul" ).eq( 0 );
+	},
+
+	_createPanel: function( id ) {
+		return $( "<div>" )
+			.attr( "id", id )
+			.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
+			.data( "ui-tabs-destroy", true );
+	},
+
+	_setupDisabled: function( disabled ) {
+		if ( $.isArray( disabled ) ) {
+			if ( !disabled.length ) {
+				disabled = false;
+			} else if ( disabled.length === this.anchors.length ) {
+				disabled = true;
+			}
+		}
+
+		// disable tabs
+		for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) {
+			if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
+				$( li )
+					.addClass( "ui-state-disabled" )
+					.attr( "aria-disabled", "true" );
+			} else {
+				$( li )
+					.removeClass( "ui-state-disabled" )
+					.removeAttr( "aria-disabled" );
+			}
+		}
+
+		this.options.disabled = disabled;
+	},
+
+	_setupEvents: function( event ) {
+		var events = {};
+		if ( event ) {
+			$.each( event.split(" "), function( index, eventName ) {
+				events[ eventName ] = "_eventHandler";
+			});
+		}
+
+		this._off( this.anchors.add( this.tabs ).add( this.panels ) );
+		// Always prevent the default action, even when disabled
+		this._on( true, this.anchors, {
+			click: function( event ) {
+				event.preventDefault();
+			}
+		});
+		this._on( this.anchors, events );
+		this._on( this.tabs, { keydown: "_tabKeydown" } );
+		this._on( this.panels, { keydown: "_panelKeydown" } );
+
+		this._focusable( this.tabs );
+		this._hoverable( this.tabs );
+	},
+
+	_setupHeightStyle: function( heightStyle ) {
+		var maxHeight,
+			parent = this.element.parent();
+
+		if ( heightStyle === "fill" ) {
+			maxHeight = parent.height();
+			maxHeight -= this.element.outerHeight() - this.element.height();
+
+			this.element.siblings( ":visible" ).each(function() {
+				var elem = $( this ),
+					position = elem.css( "position" );
+
+				if ( position === "absolute" || position === "fixed" ) {
+					return;
+				}
+				maxHeight -= elem.outerHeight( true );
+			});
+
+			this.element.children().not( this.panels ).each(function() {
+				maxHeight -= $( this ).outerHeight( true );
+			});
+
+			this.panels.each(function() {
+				$( this ).height( Math.max( 0, maxHeight -
+					$( this ).innerHeight() + $( this ).height() ) );
+			})
+			.css( "overflow", "auto" );
+		} else if ( heightStyle === "auto" ) {
+			maxHeight = 0;
+			this.panels.each(function() {
+				maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
+			}).height( maxHeight );
+		}
+	},
+
+	_eventHandler: function( event ) {
+		var options = this.options,
+			active = this.active,
+			anchor = $( event.currentTarget ),
+			tab = anchor.closest( "li" ),
+			clickedIsActive = tab[ 0 ] === active[ 0 ],
+			collapsing = clickedIsActive && options.collapsible,
+			toShow = collapsing ? $() : this._getPanelForTab( tab ),
+			toHide = !active.length ? $() : this._getPanelForTab( active ),
+			eventData = {
+				oldTab: active,
+				oldPanel: toHide,
+				newTab: collapsing ? $() : tab,
+				newPanel: toShow
+			};
+
+		event.preventDefault();
+
+		if ( tab.hasClass( "ui-state-disabled" ) ||
+				// tab is already loading
+				tab.hasClass( "ui-tabs-loading" ) ||
+				// can't switch durning an animation
+				this.running ||
+				// click on active header, but not collapsible
+				( clickedIsActive && !options.collapsible ) ||
+				// allow canceling activation
+				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
+			return;
+		}
+
+		options.active = collapsing ? false : this.tabs.index( tab );
+
+		this.active = clickedIsActive ? $() : tab;
+		if ( this.xhr ) {
+			this.xhr.abort();
+		}
+
+		if ( !toHide.length && !toShow.length ) {
+			$.error( "jQuery UI Tabs: Mismatching fragment identifier." );
+		}
+
+		if ( toShow.length ) {
+			this.load( this.tabs.index( tab ), event );
+		}
+		this._toggle( event, eventData );
+	},
+
+	// handles show/hide for selecting tabs
+	_toggle: function( event, eventData ) {
+		var that = this,
+			toShow = eventData.newPanel,
+			toHide = eventData.oldPanel;
+
+		this.running = true;
+
+		function complete() {
+			that.running = false;
+			that._trigger( "activate", event, eventData );
+		}
+
+		function show() {
+			eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );
+
+			if ( toShow.length && that.options.show ) {
+				that._show( toShow, that.options.show, complete );
+			} else {
+				toShow.show();
+				complete();
+			}
+		}
+
+		// start out by hiding, then showing, then completing
+		if ( toHide.length && this.options.hide ) {
+			this._hide( toHide, this.options.hide, function() {
+				eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
+				show();
+			});
+		} else {
+			eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
+			toHide.hide();
+			show();
+		}
+
+		toHide.attr( "aria-hidden", "true" );
+		eventData.oldTab.attr({
+			"aria-selected": "false",
+			"aria-expanded": "false"
+		});
+		// If we're switching tabs, remove the old tab from the tab order.
+		// If we're opening from collapsed state, remove the previous tab from the tab order.
+		// If we're collapsing, then keep the collapsing tab in the tab order.
+		if ( toShow.length && toHide.length ) {
+			eventData.oldTab.attr( "tabIndex", -1 );
+		} else if ( toShow.length ) {
+			this.tabs.filter(function() {
+				return $( this ).attr( "tabIndex" ) === 0;
+			})
+			.attr( "tabIndex", -1 );
+		}
+
+		toShow.attr( "aria-hidden", "false" );
+		eventData.newTab.attr({
+			"aria-selected": "true",
+			"aria-expanded": "true",
+			tabIndex: 0
+		});
+	},
+
+	_activate: function( index ) {
+		var anchor,
+			active = this._findActive( index );
+
+		// trying to activate the already active panel
+		if ( active[ 0 ] === this.active[ 0 ] ) {
+			return;
+		}
+
+		// trying to collapse, simulate a click on the current active header
+		if ( !active.length ) {
+			active = this.active;
+		}
+
+		anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
+		this._eventHandler({
+			target: anchor,
+			currentTarget: anchor,
+			preventDefault: $.noop
+		});
+	},
+
+	_findActive: function( index ) {
+		return index === false ? $() : this.tabs.eq( index );
+	},
+
+	_getIndex: function( index ) {
+		// meta-function to give users option to provide a href string instead of a numerical index.
+		if ( typeof index === "string" ) {
+			index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
+		}
+
+		return index;
+	},
+
+	_destroy: function() {
+		if ( this.xhr ) {
+			this.xhr.abort();
+		}
+
+		this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );
+
+		this.tablist
+			.removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
+			.removeAttr( "role" );
+
+		this.anchors
+			.removeClass( "ui-tabs-anchor" )
+			.removeAttr( "role" )
+			.removeAttr( "tabIndex" )
+			.removeUniqueId();
+
+		this.tablist.unbind( this.eventNamespace );
+
+		this.tabs.add( this.panels ).each(function() {
+			if ( $.data( this, "ui-tabs-destroy" ) ) {
+				$( this ).remove();
+			} else {
+				$( this )
+					.removeClass( "ui-state-default ui-state-active ui-state-disabled " +
+						"ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
+					.removeAttr( "tabIndex" )
+					.removeAttr( "aria-live" )
+					.removeAttr( "aria-busy" )
+					.removeAttr( "aria-selected" )
+					.removeAttr( "aria-labelledby" )
+					.removeAttr( "aria-hidden" )
+					.removeAttr( "aria-expanded" )
+					.removeAttr( "role" );
+			}
+		});
+
+		this.tabs.each(function() {
+			var li = $( this ),
+				prev = li.data( "ui-tabs-aria-controls" );
+			if ( prev ) {
+				li
+					.attr( "aria-controls", prev )
+					.removeData( "ui-tabs-aria-controls" );
+			} else {
+				li.removeAttr( "aria-controls" );
+			}
+		});
+
+		this.panels.show();
+
+		if ( this.options.heightStyle !== "content" ) {
+			this.panels.css( "height", "" );
+		}
+	},
+
+	enable: function( index ) {
+		var disabled = this.options.disabled;
+		if ( disabled === false ) {
+			return;
+		}
+
+		if ( index === undefined ) {
+			disabled = false;
+		} else {
+			index = this._getIndex( index );
+			if ( $.isArray( disabled ) ) {
+				disabled = $.map( disabled, function( num ) {
+					return num !== index ? num : null;
+				});
+			} else {
+				disabled = $.map( this.tabs, function( li, num ) {
+					return num !== index ? num : null;
+				});
+			}
+		}
+		this._setupDisabled( disabled );
+	},
+
+	disable: function( index ) {
+		var disabled = this.options.disabled;
+		if ( disabled === true ) {
+			return;
+		}
+
+		if ( index === undefined ) {
+			disabled = true;
+		} else {
+			index = this._getIndex( index );
+			if ( $.inArray( index, disabled ) !== -1 ) {
+				return;
+			}
+			if ( $.isArray( disabled ) ) {
+				disabled = $.merge( [ index ], disabled ).sort();
+			} else {
+				disabled = [ index ];
+			}
+		}
+		this._setupDisabled( disabled );
+	},
+
+	load: function( index, event ) {
+		index = this._getIndex( index );
+		var that = this,
+			tab = this.tabs.eq( index ),
+			anchor = tab.find( ".ui-tabs-anchor" ),
+			panel = this._getPanelForTab( tab ),
+			eventData = {
+				tab: tab,
+				panel: panel
+			},
+			complete = function( jqXHR, status ) {
+				if ( status === "abort" ) {
+					that.panels.stop( false, true );
+				}
+
+				tab.removeClass( "ui-tabs-loading" );
+				panel.removeAttr( "aria-busy" );
+
+				if ( jqXHR === that.xhr ) {
+					delete that.xhr;
+				}
+			};
+
+		// not remote
+		if ( this._isLocal( anchor[ 0 ] ) ) {
+			return;
+		}
+
+		this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
+
+		// support: jQuery <1.8
+		// jQuery <1.8 returns false if the request is canceled in beforeSend,
+		// but as of 1.8, $.ajax() always returns a jqXHR object.
+		if ( this.xhr && this.xhr.statusText !== "canceled" ) {
+			tab.addClass( "ui-tabs-loading" );
+			panel.attr( "aria-busy", "true" );
+
+			this.xhr
+				.done(function( response, status, jqXHR ) {
+					// support: jQuery <1.8
+					// http://bugs.jquery.com/ticket/11778
+					setTimeout(function() {
+						panel.html( response );
+						that._trigger( "load", event, eventData );
+
+						complete( jqXHR, status );
+					}, 1 );
+				})
+				.fail(function( jqXHR, status ) {
+					// support: jQuery <1.8
+					// http://bugs.jquery.com/ticket/11778
+					setTimeout(function() {
+						complete( jqXHR, status );
+					}, 1 );
+				});
+		}
+	},
+
+	_ajaxSettings: function( anchor, event, eventData ) {
+		var that = this;
+		return {
+			url: anchor.attr( "href" ),
+			beforeSend: function( jqXHR, settings ) {
+				return that._trigger( "beforeLoad", event,
+					$.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) );
+			}
+		};
+	},
+
+	_getPanelForTab: function( tab ) {
+		var id = $( tab ).attr( "aria-controls" );
+		return this.element.find( this._sanitizeSelector( "#" + id ) );
+	}
+});
+
+
+/*!
+ * jQuery UI Tooltip 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/tooltip/
+ */
+
+
+var tooltip = $.widget( "ui.tooltip", {
+	version: "1.11.4",
+	options: {
+		content: function() {
+			// support: IE<9, Opera in jQuery <1.7
+			// .text() can't accept undefined, so coerce to a string
+			var title = $( this ).attr( "title" ) || "";
+			// Escape title, since we're going from an attribute to raw HTML
+			return $( "<a>" ).text( title ).html();
+		},
+		hide: true,
+		// Disabled elements have inconsistent behavior across browsers (#8661)
+		items: "[title]:not([disabled])",
+		position: {
+			my: "left top+15",
+			at: "left bottom",
+			collision: "flipfit flip"
+		},
+		show: true,
+		tooltipClass: null,
+		track: false,
+
+		// callbacks
+		close: null,
+		open: null
+	},
+
+	_addDescribedBy: function( elem, id ) {
+		var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
+		describedby.push( id );
+		elem
+			.data( "ui-tooltip-id", id )
+			.attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
+	},
+
+	_removeDescribedBy: function( elem ) {
+		var id = elem.data( "ui-tooltip-id" ),
+			describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
+			index = $.inArray( id, describedby );
+
+		if ( index !== -1 ) {
+			describedby.splice( index, 1 );
+		}
+
+		elem.removeData( "ui-tooltip-id" );
+		describedby = $.trim( describedby.join( " " ) );
+		if ( describedby ) {
+			elem.attr( "aria-describedby", describedby );
+		} else {
+			elem.removeAttr( "aria-describedby" );
+		}
+	},
+
+	_create: function() {
+		this._on({
+			mouseover: "open",
+			focusin: "open"
+		});
+
+		// IDs of generated tooltips, needed for destroy
+		this.tooltips = {};
+
+		// IDs of parent tooltips where we removed the title attribute
+		this.parents = {};
+
+		if ( this.options.disabled ) {
+			this._disable();
+		}
+
+		// Append the aria-live region so tooltips announce correctly
+		this.liveRegion = $( "<div>" )
+			.attr({
+				role: "log",
+				"aria-live": "assertive",
+				"aria-relevant": "additions"
+			})
+			.addClass( "ui-helper-hidden-accessible" )
+			.appendTo( this.document[ 0 ].body );
+	},
+
+	_setOption: function( key, value ) {
+		var that = this;
+
+		if ( key === "disabled" ) {
+			this[ value ? "_disable" : "_enable" ]();
+			this.options[ key ] = value;
+			// disable element style changes
+			return;
+		}
+
+		this._super( key, value );
+
+		if ( key === "content" ) {
+			$.each( this.tooltips, function( id, tooltipData ) {
+				that._updateContent( tooltipData.element );
+			});
+		}
+	},
+
+	_disable: function() {
+		var that = this;
+
+		// close open tooltips
+		$.each( this.tooltips, function( id, tooltipData ) {
+			var event = $.Event( "blur" );
+			event.target = event.currentTarget = tooltipData.element[ 0 ];
+			that.close( event, true );
+		});
+
+		// remove title attributes to prevent native tooltips
+		this.element.find( this.options.items ).addBack().each(function() {
+			var element = $( this );
+			if ( element.is( "[title]" ) ) {
+				element
+					.data( "ui-tooltip-title", element.attr( "title" ) )
+					.removeAttr( "title" );
+			}
+		});
+	},
+
+	_enable: function() {
+		// restore title attributes
+		this.element.find( this.options.items ).addBack().each(function() {
+			var element = $( this );
+			if ( element.data( "ui-tooltip-title" ) ) {
+				element.attr( "title", element.data( "ui-tooltip-title" ) );
+			}
+		});
+	},
+
+	open: function( event ) {
+		var that = this,
+			target = $( event ? event.target : this.element )
+				// we need closest here due to mouseover bubbling,
+				// but always pointing at the same event target
+				.closest( this.options.items );
+
+		// No element to show a tooltip for or the tooltip is already open
+		if ( !target.length || target.data( "ui-tooltip-id" ) ) {
+			return;
+		}
+
+		if ( target.attr( "title" ) ) {
+			target.data( "ui-tooltip-title", target.attr( "title" ) );
+		}
+
+		target.data( "ui-tooltip-open", true );
+
+		// kill parent tooltips, custom or native, for hover
+		if ( event && event.type === "mouseover" ) {
+			target.parents().each(function() {
+				var parent = $( this ),
+					blurEvent;
+				if ( parent.data( "ui-tooltip-open" ) ) {
+					blurEvent = $.Event( "blur" );
+					blurEvent.target = blurEvent.currentTarget = this;
+					that.close( blurEvent, true );
+				}
+				if ( parent.attr( "title" ) ) {
+					parent.uniqueId();
+					that.parents[ this.id ] = {
+						element: this,
+						title: parent.attr( "title" )
+					};
+					parent.attr( "title", "" );
+				}
+			});
+		}
+
+		this._registerCloseHandlers( event, target );
+		this._updateContent( target, event );
+	},
+
+	_updateContent: function( target, event ) {
+		var content,
+			contentOption = this.options.content,
+			that = this,
+			eventType = event ? event.type : null;
+
+		if ( typeof contentOption === "string" ) {
+			return this._open( event, target, contentOption );
+		}
+
+		content = contentOption.call( target[0], function( response ) {
+
+			// IE may instantly serve a cached response for ajax requests
+			// delay this call to _open so the other call to _open runs first
+			that._delay(function() {
+
+				// Ignore async response if tooltip was closed already
+				if ( !target.data( "ui-tooltip-open" ) ) {
+					return;
+				}
+
+				// jQuery creates a special event for focusin when it doesn't
+				// exist natively. To improve performance, the native event
+				// object is reused and the type is changed. Therefore, we can't
+				// rely on the type being correct after the event finished
+				// bubbling, so we set it back to the previous value. (#8740)
+				if ( event ) {
+					event.type = eventType;
+				}
+				this._open( event, target, response );
+			});
+		});
+		if ( content ) {
+			this._open( event, target, content );
+		}
+	},
+
+	_open: function( event, target, content ) {
+		var tooltipData, tooltip, delayedShow, a11yContent,
+			positionOption = $.extend( {}, this.options.position );
+
+		if ( !content ) {
+			return;
+		}
+
+		// Content can be updated multiple times. If the tooltip already
+		// exists, then just update the content and bail.
+		tooltipData = this._find( target );
+		if ( tooltipData ) {
+			tooltipData.tooltip.find( ".ui-tooltip-content" ).html( content );
+			return;
+		}
+
+		// if we have a title, clear it to prevent the native tooltip
+		// we have to check first to avoid defining a title if none exists
+		// (we don't want to cause an element to start matching [title])
+		//
+		// We use removeAttr only for key events, to allow IE to export the correct
+		// accessible attributes. For mouse events, set to empty string to avoid
+		// native tooltip showing up (happens only when removing inside mouseover).
+		if ( target.is( "[title]" ) ) {
+			if ( event && event.type === "mouseover" ) {
+				target.attr( "title", "" );
+			} else {
+				target.removeAttr( "title" );
+			}
+		}
+
+		tooltipData = this._tooltip( target );
+		tooltip = tooltipData.tooltip;
+		this._addDescribedBy( target, tooltip.attr( "id" ) );
+		tooltip.find( ".ui-tooltip-content" ).html( content );
+
+		// Support: Voiceover on OS X, JAWS on IE <= 9
+		// JAWS announces deletions even when aria-relevant="additions"
+		// Voiceover will sometimes re-read the entire log region's contents from the beginning
+		this.liveRegion.children().hide();
+		if ( content.clone ) {
+			a11yContent = content.clone();
+			a11yContent.removeAttr( "id" ).find( "[id]" ).removeAttr( "id" );
+		} else {
+			a11yContent = content;
+		}
+		$( "<div>" ).html( a11yContent ).appendTo( this.liveRegion );
+
+		function position( event ) {
+			positionOption.of = event;
+			if ( tooltip.is( ":hidden" ) ) {
+				return;
+			}
+			tooltip.position( positionOption );
+		}
+		if ( this.options.track && event && /^mouse/.test( event.type ) ) {
+			this._on( this.document, {
+				mousemove: position
+			});
+			// trigger once to override element-relative positioning
+			position( event );
+		} else {
+			tooltip.position( $.extend({
+				of: target
+			}, this.options.position ) );
+		}
+
+		tooltip.hide();
+
+		this._show( tooltip, this.options.show );
+		// Handle tracking tooltips that are shown with a delay (#8644). As soon
+		// as the tooltip is visible, position the tooltip using the most recent
+		// event.
+		if ( this.options.show && this.options.show.delay ) {
+			delayedShow = this.delayedShow = setInterval(function() {
+				if ( tooltip.is( ":visible" ) ) {
+					position( positionOption.of );
+					clearInterval( delayedShow );
+				}
+			}, $.fx.interval );
+		}
+
+		this._trigger( "open", event, { tooltip: tooltip } );
+	},
+
+	_registerCloseHandlers: function( event, target ) {
+		var events = {
+			keyup: function( event ) {
+				if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
+					var fakeEvent = $.Event(event);
+					fakeEvent.currentTarget = target[0];
+					this.close( fakeEvent, true );
+				}
+			}
+		};
+
+		// Only bind remove handler for delegated targets. Non-delegated
+		// tooltips will handle this in destroy.
+		if ( target[ 0 ] !== this.element[ 0 ] ) {
+			events.remove = function() {
+				this._removeTooltip( this._find( target ).tooltip );
+			};
+		}
+
+		if ( !event || event.type === "mouseover" ) {
+			events.mouseleave = "close";
+		}
+		if ( !event || event.type === "focusin" ) {
+			events.focusout = "close";
+		}
+		this._on( true, target, events );
+	},
+
+	close: function( event ) {
+		var tooltip,
+			that = this,
+			target = $( event ? event.currentTarget : this.element ),
+			tooltipData = this._find( target );
+
+		// The tooltip may already be closed
+		if ( !tooltipData ) {
+
+			// We set ui-tooltip-open immediately upon open (in open()), but only set the
+			// additional data once there's actually content to show (in _open()). So even if the
+			// tooltip doesn't have full data, we always remove ui-tooltip-open in case we're in
+			// the period between open() and _open().
+			target.removeData( "ui-tooltip-open" );
+			return;
+		}
+
+		tooltip = tooltipData.tooltip;
+
+		// disabling closes the tooltip, so we need to track when we're closing
+		// to avoid an infinite loop in case the tooltip becomes disabled on close
+		if ( tooltipData.closing ) {
+			return;
+		}
+
+		// Clear the interval for delayed tracking tooltips
+		clearInterval( this.delayedShow );
+
+		// only set title if we had one before (see comment in _open())
+		// If the title attribute has changed since open(), don't restore
+		if ( target.data( "ui-tooltip-title" ) && !target.attr( "title" ) ) {
+			target.attr( "title", target.data( "ui-tooltip-title" ) );
+		}
+
+		this._removeDescribedBy( target );
+
+		tooltipData.hiding = true;
+		tooltip.stop( true );
+		this._hide( tooltip, this.options.hide, function() {
+			that._removeTooltip( $( this ) );
+		});
+
+		target.removeData( "ui-tooltip-open" );
+		this._off( target, "mouseleave focusout keyup" );
+
+		// Remove 'remove' binding only on delegated targets
+		if ( target[ 0 ] !== this.element[ 0 ] ) {
+			this._off( target, "remove" );
+		}
+		this._off( this.document, "mousemove" );
+
+		if ( event && event.type === "mouseleave" ) {
+			$.each( this.parents, function( id, parent ) {
+				$( parent.element ).attr( "title", parent.title );
+				delete that.parents[ id ];
+			});
+		}
+
+		tooltipData.closing = true;
+		this._trigger( "close", event, { tooltip: tooltip } );
+		if ( !tooltipData.hiding ) {
+			tooltipData.closing = false;
+		}
+	},
+
+	_tooltip: function( element ) {
+		var tooltip = $( "<div>" )
+				.attr( "role", "tooltip" )
+				.addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
+					( this.options.tooltipClass || "" ) ),
+			id = tooltip.uniqueId().attr( "id" );
+
+		$( "<div>" )
+			.addClass( "ui-tooltip-content" )
+			.appendTo( tooltip );
+
+		tooltip.appendTo( this.document[0].body );
+
+		return this.tooltips[ id ] = {
+			element: element,
+			tooltip: tooltip
+		};
+	},
+
+	_find: function( target ) {
+		var id = target.data( "ui-tooltip-id" );
+		return id ? this.tooltips[ id ] : null;
+	},
+
+	_removeTooltip: function( tooltip ) {
+		tooltip.remove();
+		delete this.tooltips[ tooltip.attr( "id" ) ];
+	},
+
+	_destroy: function() {
+		var that = this;
+
+		// close open tooltips
+		$.each( this.tooltips, function( id, tooltipData ) {
+			// Delegate to close method to handle common cleanup
+			var event = $.Event( "blur" ),
+				element = tooltipData.element;
+			event.target = event.currentTarget = element[ 0 ];
+			that.close( event, true );
+
+			// Remove immediately; destroying an open tooltip doesn't use the
+			// hide animation
+			$( "#" + id ).remove();
+
+			// Restore the title
+			if ( element.data( "ui-tooltip-title" ) ) {
+				// If the title attribute has changed since open(), don't restore
+				if ( !element.attr( "title" ) ) {
+					element.attr( "title", element.data( "ui-tooltip-title" ) );
+				}
+				element.removeData( "ui-tooltip-title" );
+			}
+		});
+		this.liveRegion.remove();
+	}
+});
+
+
+/*!
+ * jQuery UI Effects 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/category/effects-core/
+ */
+
+
+var dataSpace = "ui-effects-",
+
+	// Create a local jQuery because jQuery Color relies on it and the
+	// global may not exist with AMD and a custom build (#10199)
+	jQuery = $;
+
+$.effects = {
+	effect: {}
+};
+
+/*!
+ * jQuery Color Animations v2.1.2
+ * https://github.com/jquery/jquery-color
+ *
+ * Copyright 2014 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * Date: Wed Jan 16 08:47:09 2013 -0600
+ */
+(function( jQuery, undefined ) {
+
+	var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",
+
+	// plusequals test for += 100 -= 100
+	rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,
+	// a set of RE's that can match strings and generate color tuples.
+	stringParsers = [ {
+			re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
+			parse: function( execResult ) {
+				return [
+					execResult[ 1 ],
+					execResult[ 2 ],
+					execResult[ 3 ],
+					execResult[ 4 ]
+				];
+			}
+		}, {
+			re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
+			parse: function( execResult ) {
+				return [
+					execResult[ 1 ] * 2.55,
+					execResult[ 2 ] * 2.55,
+					execResult[ 3 ] * 2.55,
+					execResult[ 4 ]
+				];
+			}
+		}, {
+			// this regex ignores A-F because it's compared against an already lowercased string
+			re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,
+			parse: function( execResult ) {
+				return [
+					parseInt( execResult[ 1 ], 16 ),
+					parseInt( execResult[ 2 ], 16 ),
+					parseInt( execResult[ 3 ], 16 )
+				];
+			}
+		}, {
+			// this regex ignores A-F because it's compared against an already lowercased string
+			re: /#([a-f0-9])([a-f0-9])([a-f0-9])/,
+			parse: function( execResult ) {
+				return [
+					parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
+					parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
+					parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
+				];
+			}
+		}, {
+			re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
+			space: "hsla",
+			parse: function( execResult ) {
+				return [
+					execResult[ 1 ],
+					execResult[ 2 ] / 100,
+					execResult[ 3 ] / 100,
+					execResult[ 4 ]
+				];
+			}
+		} ],
+
+	// jQuery.Color( )
+	color = jQuery.Color = function( color, green, blue, alpha ) {
+		return new jQuery.Color.fn.parse( color, green, blue, alpha );
+	},
+	spaces = {
+		rgba: {
+			props: {
+				red: {
+					idx: 0,
+					type: "byte"
+				},
+				green: {
+					idx: 1,
+					type: "byte"
+				},
+				blue: {
+					idx: 2,
+					type: "byte"
+				}
+			}
+		},
+
+		hsla: {
+			props: {
+				hue: {
+					idx: 0,
+					type: "degrees"
+				},
+				saturation: {
+					idx: 1,
+					type: "percent"
+				},
+				lightness: {
+					idx: 2,
+					type: "percent"
+				}
+			}
+		}
+	},
+	propTypes = {
+		"byte": {
+			floor: true,
+			max: 255
+		},
+		"percent": {
+			max: 1
+		},
+		"degrees": {
+			mod: 360,
+			floor: true
+		}
+	},
+	support = color.support = {},
+
+	// element for support tests
+	supportElem = jQuery( "<p>" )[ 0 ],
+
+	// colors = jQuery.Color.names
+	colors,
+
+	// local aliases of functions called often
+	each = jQuery.each;
+
+// determine rgba support immediately
+supportElem.style.cssText = "background-color:rgba(1,1,1,.5)";
+support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1;
+
+// define cache name and alpha properties
+// for rgba and hsla spaces
+each( spaces, function( spaceName, space ) {
+	space.cache = "_" + spaceName;
+	space.props.alpha = {
+		idx: 3,
+		type: "percent",
+		def: 1
+	};
+});
+
+function clamp( value, prop, allowEmpty ) {
+	var type = propTypes[ prop.type ] || {};
+
+	if ( value == null ) {
+		return (allowEmpty || !prop.def) ? null : prop.def;
+	}
+
+	// ~~ is an short way of doing floor for positive numbers
+	value = type.floor ? ~~value : parseFloat( value );
+
+	// IE will pass in empty strings as value for alpha,
+	// which will hit this case
+	if ( isNaN( value ) ) {
+		return prop.def;
+	}
+
+	if ( type.mod ) {
+		// we add mod before modding to make sure that negatives values
+		// get converted properly: -10 -> 350
+		return (value + type.mod) % type.mod;
+	}
+
+	// for now all property types without mod have min and max
+	return 0 > value ? 0 : type.max < value ? type.max : value;
+}
+
+function stringParse( string ) {
+	var inst = color(),
+		rgba = inst._rgba = [];
+
+	string = string.toLowerCase();
+
+	each( stringParsers, function( i, parser ) {
+		var parsed,
+			match = parser.re.exec( string ),
+			values = match && parser.parse( match ),
+			spaceName = parser.space || "rgba";
+
+		if ( values ) {
+			parsed = inst[ spaceName ]( values );
+
+			// if this was an rgba parse the assignment might happen twice
+			// oh well....
+			inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ];
+			rgba = inst._rgba = parsed._rgba;
+
+			// exit each( stringParsers ) here because we matched
+			return false;
+		}
+	});
+
+	// Found a stringParser that handled it
+	if ( rgba.length ) {
+
+		// if this came from a parsed string, force "transparent" when alpha is 0
+		// chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
+		if ( rgba.join() === "0,0,0,0" ) {
+			jQuery.extend( rgba, colors.transparent );
+		}
+		return inst;
+	}
+
+	// named colors
+	return colors[ string ];
+}
+
+color.fn = jQuery.extend( color.prototype, {
+	parse: function( red, green, blue, alpha ) {
+		if ( red === undefined ) {
+			this._rgba = [ null, null, null, null ];
+			return this;
+		}
+		if ( red.jquery || red.nodeType ) {
+			red = jQuery( red ).css( green );
+			green = undefined;
+		}
+
+		var inst = this,
+			type = jQuery.type( red ),
+			rgba = this._rgba = [];
+
+		// more than 1 argument specified - assume ( red, green, blue, alpha )
+		if ( green !== undefined ) {
+			red = [ red, green, blue, alpha ];
+			type = "array";
+		}
+
+		if ( type === "string" ) {
+			return this.parse( stringParse( red ) || colors._default );
+		}
+
+		if ( type === "array" ) {
+			each( spaces.rgba.props, function( key, prop ) {
+				rgba[ prop.idx ] = clamp( red[ prop.idx ], prop );
+			});
+			return this;
+		}
+
+		if ( type === "object" ) {
+			if ( red instanceof color ) {
+				each( spaces, function( spaceName, space ) {
+					if ( red[ space.cache ] ) {
+						inst[ space.cache ] = red[ space.cache ].slice();
+					}
+				});
+			} else {
+				each( spaces, function( spaceName, space ) {
+					var cache = space.cache;
+					each( space.props, function( key, prop ) {
+
+						// if the cache doesn't exist, and we know how to convert
+						if ( !inst[ cache ] && space.to ) {
+
+							// if the value was null, we don't need to copy it
+							// if the key was alpha, we don't need to copy it either
+							if ( key === "alpha" || red[ key ] == null ) {
+								return;
+							}
+							inst[ cache ] = space.to( inst._rgba );
+						}
+
+						// this is the only case where we allow nulls for ALL properties.
+						// call clamp with alwaysAllowEmpty
+						inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );
+					});
+
+					// everything defined but alpha?
+					if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) {
+						// use the default of 1
+						inst[ cache ][ 3 ] = 1;
+						if ( space.from ) {
+							inst._rgba = space.from( inst[ cache ] );
+						}
+					}
+				});
+			}
+			return this;
+		}
+	},
+	is: function( compare ) {
+		var is = color( compare ),
+			same = true,
+			inst = this;
+
+		each( spaces, function( _, space ) {
+			var localCache,
+				isCache = is[ space.cache ];
+			if (isCache) {
+				localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || [];
+				each( space.props, function( _, prop ) {
+					if ( isCache[ prop.idx ] != null ) {
+						same = ( isCache[ prop.idx ] === localCache[ prop.idx ] );
+						return same;
+					}
+				});
+			}
+			return same;
+		});
+		return same;
+	},
+	_space: function() {
+		var used = [],
+			inst = this;
+		each( spaces, function( spaceName, space ) {
+			if ( inst[ space.cache ] ) {
+				used.push( spaceName );
+			}
+		});
+		return used.pop();
+	},
+	transition: function( other, distance ) {
+		var end = color( other ),
+			spaceName = end._space(),
+			space = spaces[ spaceName ],
+			startColor = this.alpha() === 0 ? color( "transparent" ) : this,
+			start = startColor[ space.cache ] || space.to( startColor._rgba ),
+			result = start.slice();
+
+		end = end[ space.cache ];
+		each( space.props, function( key, prop ) {
+			var index = prop.idx,
+				startValue = start[ index ],
+				endValue = end[ index ],
+				type = propTypes[ prop.type ] || {};
+
+			// if null, don't override start value
+			if ( endValue === null ) {
+				return;
+			}
+			// if null - use end
+			if ( startValue === null ) {
+				result[ index ] = endValue;
+			} else {
+				if ( type.mod ) {
+					if ( endValue - startValue > type.mod / 2 ) {
+						startValue += type.mod;
+					} else if ( startValue - endValue > type.mod / 2 ) {
+						startValue -= type.mod;
+					}
+				}
+				result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop );
+			}
+		});
+		return this[ spaceName ]( result );
+	},
+	blend: function( opaque ) {
+		// if we are already opaque - return ourself
+		if ( this._rgba[ 3 ] === 1 ) {
+			return this;
+		}
+
+		var rgb = this._rgba.slice(),
+			a = rgb.pop(),
+			blend = color( opaque )._rgba;
+
+		return color( jQuery.map( rgb, function( v, i ) {
+			return ( 1 - a ) * blend[ i ] + a * v;
+		}));
+	},
+	toRgbaString: function() {
+		var prefix = "rgba(",
+			rgba = jQuery.map( this._rgba, function( v, i ) {
+				return v == null ? ( i > 2 ? 1 : 0 ) : v;
+			});
+
+		if ( rgba[ 3 ] === 1 ) {
+			rgba.pop();
+			prefix = "rgb(";
+		}
+
+		return prefix + rgba.join() + ")";
+	},
+	toHslaString: function() {
+		var prefix = "hsla(",
+			hsla = jQuery.map( this.hsla(), function( v, i ) {
+				if ( v == null ) {
+					v = i > 2 ? 1 : 0;
+				}
+
+				// catch 1 and 2
+				if ( i && i < 3 ) {
+					v = Math.round( v * 100 ) + "%";
+				}
+				return v;
+			});
+
+		if ( hsla[ 3 ] === 1 ) {
+			hsla.pop();
+			prefix = "hsl(";
+		}
+		return prefix + hsla.join() + ")";
+	},
+	toHexString: function( includeAlpha ) {
+		var rgba = this._rgba.slice(),
+			alpha = rgba.pop();
+
+		if ( includeAlpha ) {
+			rgba.push( ~~( alpha * 255 ) );
+		}
+
+		return "#" + jQuery.map( rgba, function( v ) {
+
+			// default to 0 when nulls exist
+			v = ( v || 0 ).toString( 16 );
+			return v.length === 1 ? "0" + v : v;
+		}).join("");
+	},
+	toString: function() {
+		return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString();
+	}
+});
+color.fn.parse.prototype = color.fn;
+
+// hsla conversions adapted from:
+// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021
+
+function hue2rgb( p, q, h ) {
+	h = ( h + 1 ) % 1;
+	if ( h * 6 < 1 ) {
+		return p + ( q - p ) * h * 6;
+	}
+	if ( h * 2 < 1) {
+		return q;
+	}
+	if ( h * 3 < 2 ) {
+		return p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6;
+	}
+	return p;
+}
+
+spaces.hsla.to = function( rgba ) {
+	if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {
+		return [ null, null, null, rgba[ 3 ] ];
+	}
+	var r = rgba[ 0 ] / 255,
+		g = rgba[ 1 ] / 255,
+		b = rgba[ 2 ] / 255,
+		a = rgba[ 3 ],
+		max = Math.max( r, g, b ),
+		min = Math.min( r, g, b ),
+		diff = max - min,
+		add = max + min,
+		l = add * 0.5,
+		h, s;
+
+	if ( min === max ) {
+		h = 0;
+	} else if ( r === max ) {
+		h = ( 60 * ( g - b ) / diff ) + 360;
+	} else if ( g === max ) {
+		h = ( 60 * ( b - r ) / diff ) + 120;
+	} else {
+		h = ( 60 * ( r - g ) / diff ) + 240;
+	}
+
+	// chroma (diff) == 0 means greyscale which, by definition, saturation = 0%
+	// otherwise, saturation is based on the ratio of chroma (diff) to lightness (add)
+	if ( diff === 0 ) {
+		s = 0;
+	} else if ( l <= 0.5 ) {
+		s = diff / add;
+	} else {
+		s = diff / ( 2 - add );
+	}
+	return [ Math.round(h) % 360, s, l, a == null ? 1 : a ];
+};
+
+spaces.hsla.from = function( hsla ) {
+	if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {
+		return [ null, null, null, hsla[ 3 ] ];
+	}
+	var h = hsla[ 0 ] / 360,
+		s = hsla[ 1 ],
+		l = hsla[ 2 ],
+		a = hsla[ 3 ],
+		q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,
+		p = 2 * l - q;
+
+	return [
+		Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),
+		Math.round( hue2rgb( p, q, h ) * 255 ),
+		Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),
+		a
+	];
+};
+
+each( spaces, function( spaceName, space ) {
+	var props = space.props,
+		cache = space.cache,
+		to = space.to,
+		from = space.from;
+
+	// makes rgba() and hsla()
+	color.fn[ spaceName ] = function( value ) {
+
+		// generate a cache for this space if it doesn't exist
+		if ( to && !this[ cache ] ) {
+			this[ cache ] = to( this._rgba );
+		}
+		if ( value === undefined ) {
+			return this[ cache ].slice();
+		}
+
+		var ret,
+			type = jQuery.type( value ),
+			arr = ( type === "array" || type === "object" ) ? value : arguments,
+			local = this[ cache ].slice();
+
+		each( props, function( key, prop ) {
+			var val = arr[ type === "object" ? key : prop.idx ];
+			if ( val == null ) {
+				val = local[ prop.idx ];
+			}
+			local[ prop.idx ] = clamp( val, prop );
+		});
+
+		if ( from ) {
+			ret = color( from( local ) );
+			ret[ cache ] = local;
+			return ret;
+		} else {
+			return color( local );
+		}
+	};
+
+	// makes red() green() blue() alpha() hue() saturation() lightness()
+	each( props, function( key, prop ) {
+		// alpha is included in more than one space
+		if ( color.fn[ key ] ) {
+			return;
+		}
+		color.fn[ key ] = function( value ) {
+			var vtype = jQuery.type( value ),
+				fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ),
+				local = this[ fn ](),
+				cur = local[ prop.idx ],
+				match;
+
+			if ( vtype === "undefined" ) {
+				return cur;
+			}
+
+			if ( vtype === "function" ) {
+				value = value.call( this, cur );
+				vtype = jQuery.type( value );
+			}
+			if ( value == null && prop.empty ) {
+				return this;
+			}
+			if ( vtype === "string" ) {
+				match = rplusequals.exec( value );
+				if ( match ) {
+					value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 );
+				}
+			}
+			local[ prop.idx ] = value;
+			return this[ fn ]( local );
+		};
+	});
+});
+
+// add cssHook and .fx.step function for each named hook.
+// accept a space separated string of properties
+color.hook = function( hook ) {
+	var hooks = hook.split( " " );
+	each( hooks, function( i, hook ) {
+		jQuery.cssHooks[ hook ] = {
+			set: function( elem, value ) {
+				var parsed, curElem,
+					backgroundColor = "";
+
+				if ( value !== "transparent" && ( jQuery.type( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) {
+					value = color( parsed || value );
+					if ( !support.rgba && value._rgba[ 3 ] !== 1 ) {
+						curElem = hook === "backgroundColor" ? elem.parentNode : elem;
+						while (
+							(backgroundColor === "" || backgroundColor === "transparent") &&
+							curElem && curElem.style
+						) {
+							try {
+								backgroundColor = jQuery.css( curElem, "backgroundColor" );
+								curElem = curElem.parentNode;
+							} catch ( e ) {
+							}
+						}
+
+						value = value.blend( backgroundColor && backgroundColor !== "transparent" ?
+							backgroundColor :
+							"_default" );
+					}
+
+					value = value.toRgbaString();
+				}
+				try {
+					elem.style[ hook ] = value;
+				} catch ( e ) {
+					// wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit'
+				}
+			}
+		};
+		jQuery.fx.step[ hook ] = function( fx ) {
+			if ( !fx.colorInit ) {
+				fx.start = color( fx.elem, hook );
+				fx.end = color( fx.end );
+				fx.colorInit = true;
+			}
+			jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );
+		};
+	});
+
+};
+
+color.hook( stepHooks );
+
+jQuery.cssHooks.borderColor = {
+	expand: function( value ) {
+		var expanded = {};
+
+		each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) {
+			expanded[ "border" + part + "Color" ] = value;
+		});
+		return expanded;
+	}
+};
+
+// Basic color names only.
+// Usage of any of the other color names requires adding yourself or including
+// jquery.color.svg-names.js.
+colors = jQuery.Color.names = {
+	// 4.1. Basic color keywords
+	aqua: "#00ffff",
+	black: "#000000",
+	blue: "#0000ff",
+	fuchsia: "#ff00ff",
+	gray: "#808080",
+	green: "#008000",
+	lime: "#00ff00",
+	maroon: "#800000",
+	navy: "#000080",
+	olive: "#808000",
+	purple: "#800080",
+	red: "#ff0000",
+	silver: "#c0c0c0",
+	teal: "#008080",
+	white: "#ffffff",
+	yellow: "#ffff00",
+
+	// 4.2.3. "transparent" color keyword
+	transparent: [ null, null, null, 0 ],
+
+	_default: "#ffffff"
+};
+
+})( jQuery );
+
+/******************************************************************************/
+/****************************** CLASS ANIMATIONS ******************************/
+/******************************************************************************/
+(function() {
+
+var classAnimationActions = [ "add", "remove", "toggle" ],
+	shorthandStyles = {
+		border: 1,
+		borderBottom: 1,
+		borderColor: 1,
+		borderLeft: 1,
+		borderRight: 1,
+		borderTop: 1,
+		borderWidth: 1,
+		margin: 1,
+		padding: 1
+	};
+
+$.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) {
+	$.fx.step[ prop ] = function( fx ) {
+		if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) {
+			jQuery.style( fx.elem, prop, fx.end );
+			fx.setAttr = true;
+		}
+	};
+});
+
+function getElementStyles( elem ) {
+	var key, len,
+		style = elem.ownerDocument.defaultView ?
+			elem.ownerDocument.defaultView.getComputedStyle( elem, null ) :
+			elem.currentStyle,
+		styles = {};
+
+	if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) {
+		len = style.length;
+		while ( len-- ) {
+			key = style[ len ];
+			if ( typeof style[ key ] === "string" ) {
+				styles[ $.camelCase( key ) ] = style[ key ];
+			}
+		}
+	// support: Opera, IE <9
+	} else {
+		for ( key in style ) {
+			if ( typeof style[ key ] === "string" ) {
+				styles[ key ] = style[ key ];
+			}
+		}
+	}
+
+	return styles;
+}
+
+function styleDifference( oldStyle, newStyle ) {
+	var diff = {},
+		name, value;
+
+	for ( name in newStyle ) {
+		value = newStyle[ name ];
+		if ( oldStyle[ name ] !== value ) {
+			if ( !shorthandStyles[ name ] ) {
+				if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) {
+					diff[ name ] = value;
+				}
+			}
+		}
+	}
+
+	return diff;
+}
+
+// support: jQuery <1.8
+if ( !$.fn.addBack ) {
+	$.fn.addBack = function( selector ) {
+		return this.add( selector == null ?
+			this.prevObject : this.prevObject.filter( selector )
+		);
+	};
+}
+
+$.effects.animateClass = function( value, duration, easing, callback ) {
+	var o = $.speed( duration, easing, callback );
+
+	return this.queue( function() {
+		var animated = $( this ),
+			baseClass = animated.attr( "class" ) || "",
+			applyClassChange,
+			allAnimations = o.children ? animated.find( "*" ).addBack() : animated;
+
+		// map the animated objects to store the original styles.
+		allAnimations = allAnimations.map(function() {
+			var el = $( this );
+			return {
+				el: el,
+				start: getElementStyles( this )
+			};
+		});
+
+		// apply class change
+		applyClassChange = function() {
+			$.each( classAnimationActions, function(i, action) {
+				if ( value[ action ] ) {
+					animated[ action + "Class" ]( value[ action ] );
+				}
+			});
+		};
+		applyClassChange();
+
+		// map all animated objects again - calculate new styles and diff
+		allAnimations = allAnimations.map(function() {
+			this.end = getElementStyles( this.el[ 0 ] );
+			this.diff = styleDifference( this.start, this.end );
+			return this;
+		});
+
+		// apply original class
+		animated.attr( "class", baseClass );
+
+		// map all animated objects again - this time collecting a promise
+		allAnimations = allAnimations.map(function() {
+			var styleInfo = this,
+				dfd = $.Deferred(),
+				opts = $.extend({}, o, {
+					queue: false,
+					complete: function() {
+						dfd.resolve( styleInfo );
+					}
+				});
+
+			this.el.animate( this.diff, opts );
+			return dfd.promise();
+		});
+
+		// once all animations have completed:
+		$.when.apply( $, allAnimations.get() ).done(function() {
+
+			// set the final class
+			applyClassChange();
+
+			// for each animated element,
+			// clear all css properties that were animated
+			$.each( arguments, function() {
+				var el = this.el;
+				$.each( this.diff, function(key) {
+					el.css( key, "" );
+				});
+			});
+
+			// this is guarnteed to be there if you use jQuery.speed()
+			// it also handles dequeuing the next anim...
+			o.complete.call( animated[ 0 ] );
+		});
+	});
+};
+
+$.fn.extend({
+	addClass: (function( orig ) {
+		return function( classNames, speed, easing, callback ) {
+			return speed ?
+				$.effects.animateClass.call( this,
+					{ add: classNames }, speed, easing, callback ) :
+				orig.apply( this, arguments );
+		};
+	})( $.fn.addClass ),
+
+	removeClass: (function( orig ) {
+		return function( classNames, speed, easing, callback ) {
+			return arguments.length > 1 ?
+				$.effects.animateClass.call( this,
+					{ remove: classNames }, speed, easing, callback ) :
+				orig.apply( this, arguments );
+		};
+	})( $.fn.removeClass ),
+
+	toggleClass: (function( orig ) {
+		return function( classNames, force, speed, easing, callback ) {
+			if ( typeof force === "boolean" || force === undefined ) {
+				if ( !speed ) {
+					// without speed parameter
+					return orig.apply( this, arguments );
+				} else {
+					return $.effects.animateClass.call( this,
+						(force ? { add: classNames } : { remove: classNames }),
+						speed, easing, callback );
+				}
+			} else {
+				// without force parameter
+				return $.effects.animateClass.call( this,
+					{ toggle: classNames }, force, speed, easing );
+			}
+		};
+	})( $.fn.toggleClass ),
+
+	switchClass: function( remove, add, speed, easing, callback) {
+		return $.effects.animateClass.call( this, {
+			add: add,
+			remove: remove
+		}, speed, easing, callback );
+	}
+});
+
+})();
+
+/******************************************************************************/
+/*********************************** EFFECTS **********************************/
+/******************************************************************************/
+
+(function() {
+
+$.extend( $.effects, {
+	version: "1.11.4",
+
+	// Saves a set of properties in a data storage
+	save: function( element, set ) {
+		for ( var i = 0; i < set.length; i++ ) {
+			if ( set[ i ] !== null ) {
+				element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
+			}
+		}
+	},
+
+	// Restores a set of previously saved properties from a data storage
+	restore: function( element, set ) {
+		var val, i;
+		for ( i = 0; i < set.length; i++ ) {
+			if ( set[ i ] !== null ) {
+				val = element.data( dataSpace + set[ i ] );
+				// support: jQuery 1.6.2
+				// http://bugs.jquery.com/ticket/9917
+				// jQuery 1.6.2 incorrectly returns undefined for any falsy value.
+				// We can't differentiate between "" and 0 here, so we just assume
+				// empty string since it's likely to be a more common value...
+				if ( val === undefined ) {
+					val = "";
+				}
+				element.css( set[ i ], val );
+			}
+		}
+	},
+
+	setMode: function( el, mode ) {
+		if (mode === "toggle") {
+			mode = el.is( ":hidden" ) ? "show" : "hide";
+		}
+		return mode;
+	},
+
+	// Translates a [top,left] array into a baseline value
+	// this should be a little more flexible in the future to handle a string & hash
+	getBaseline: function( origin, original ) {
+		var y, x;
+		switch ( origin[ 0 ] ) {
+			case "top": y = 0; break;
+			case "middle": y = 0.5; break;
+			case "bottom": y = 1; break;
+			default: y = origin[ 0 ] / original.height;
+		}
+		switch ( origin[ 1 ] ) {
+			case "left": x = 0; break;
+			case "center": x = 0.5; break;
+			case "right": x = 1; break;
+			default: x = origin[ 1 ] / original.width;
+		}
+		return {
+			x: x,
+			y: y
+		};
+	},
+
+	// Wraps the element around a wrapper that copies position properties
+	createWrapper: function( element ) {
+
+		// if the element is already wrapped, return it
+		if ( element.parent().is( ".ui-effects-wrapper" )) {
+			return element.parent();
+		}
+
+		// wrap the element
+		var props = {
+				width: element.outerWidth(true),
+				height: element.outerHeight(true),
+				"float": element.css( "float" )
+			},
+			wrapper = $( "<div></div>" )
+				.addClass( "ui-effects-wrapper" )
+				.css({
+					fontSize: "100%",
+					background: "transparent",
+					border: "none",
+					margin: 0,
+					padding: 0
+				}),
+			// Store the size in case width/height are defined in % - Fixes #5245
+			size = {
+				width: element.width(),
+				height: element.height()
+			},
+			active = document.activeElement;
+
+		// support: Firefox
+		// Firefox incorrectly exposes anonymous content
+		// https://bugzilla.mozilla.org/show_bug.cgi?id=561664
+		try {
+			active.id;
+		} catch ( e ) {
+			active = document.body;
+		}
+
+		element.wrap( wrapper );
+
+		// Fixes #7595 - Elements lose focus when wrapped.
+		if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+			$( active ).focus();
+		}
+
+		wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element
+
+		// transfer positioning properties to the wrapper
+		if ( element.css( "position" ) === "static" ) {
+			wrapper.css({ position: "relative" });
+			element.css({ position: "relative" });
+		} else {
+			$.extend( props, {
+				position: element.css( "position" ),
+				zIndex: element.css( "z-index" )
+			});
+			$.each([ "top", "left", "bottom", "right" ], function(i, pos) {
+				props[ pos ] = element.css( pos );
+				if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
+					props[ pos ] = "auto";
+				}
+			});
+			element.css({
+				position: "relative",
+				top: 0,
+				left: 0,
+				right: "auto",
+				bottom: "auto"
+			});
+		}
+		element.css(size);
+
+		return wrapper.css( props ).show();
+	},
+
+	removeWrapper: function( element ) {
+		var active = document.activeElement;
+
+		if ( element.parent().is( ".ui-effects-wrapper" ) ) {
+			element.parent().replaceWith( element );
+
+			// Fixes #7595 - Elements lose focus when wrapped.
+			if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+				$( active ).focus();
+			}
+		}
+
+		return element;
+	},
+
+	setTransition: function( element, list, factor, value ) {
+		value = value || {};
+		$.each( list, function( i, x ) {
+			var unit = element.cssUnit( x );
+			if ( unit[ 0 ] > 0 ) {
+				value[ x ] = unit[ 0 ] * factor + unit[ 1 ];
+			}
+		});
+		return value;
+	}
+});
+
+// return an effect options object for the given parameters:
+function _normalizeArguments( effect, options, speed, callback ) {
+
+	// allow passing all options as the first parameter
+	if ( $.isPlainObject( effect ) ) {
+		options = effect;
+		effect = effect.effect;
+	}
+
+	// convert to an object
+	effect = { effect: effect };
+
+	// catch (effect, null, ...)
+	if ( options == null ) {
+		options = {};
+	}
+
+	// catch (effect, callback)
+	if ( $.isFunction( options ) ) {
+		callback = options;
+		speed = null;
+		options = {};
+	}
+
+	// catch (effect, speed, ?)
+	if ( typeof options === "number" || $.fx.speeds[ options ] ) {
+		callback = speed;
+		speed = options;
+		options = {};
+	}
+
+	// catch (effect, options, callback)
+	if ( $.isFunction( speed ) ) {
+		callback = speed;
+		speed = null;
+	}
+
+	// add options to effect
+	if ( options ) {
+		$.extend( effect, options );
+	}
+
+	speed = speed || options.duration;
+	effect.duration = $.fx.off ? 0 :
+		typeof speed === "number" ? speed :
+		speed in $.fx.speeds ? $.fx.speeds[ speed ] :
+		$.fx.speeds._default;
+
+	effect.complete = callback || options.complete;
+
+	return effect;
+}
+
+function standardAnimationOption( option ) {
+	// Valid standard speeds (nothing, number, named speed)
+	if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) {
+		return true;
+	}
+
+	// Invalid strings - treat as "normal" speed
+	if ( typeof option === "string" && !$.effects.effect[ option ] ) {
+		return true;
+	}
+
+	// Complete callback
+	if ( $.isFunction( option ) ) {
+		return true;
+	}
+
+	// Options hash (but not naming an effect)
+	if ( typeof option === "object" && !option.effect ) {
+		return true;
+	}
+
+	// Didn't match any standard API
+	return false;
+}
+
+$.fn.extend({
+	effect: function( /* effect, options, speed, callback */ ) {
+		var args = _normalizeArguments.apply( this, arguments ),
+			mode = args.mode,
+			queue = args.queue,
+			effectMethod = $.effects.effect[ args.effect ];
+
+		if ( $.fx.off || !effectMethod ) {
+			// delegate to the original method (e.g., .show()) if possible
+			if ( mode ) {
+				return this[ mode ]( args.duration, args.complete );
+			} else {
+				return this.each( function() {
+					if ( args.complete ) {
+						args.complete.call( this );
+					}
+				});
+			}
+		}
+
+		function run( next ) {
+			var elem = $( this ),
+				complete = args.complete,
+				mode = args.mode;
+
+			function done() {
+				if ( $.isFunction( complete ) ) {
+					complete.call( elem[0] );
+				}
+				if ( $.isFunction( next ) ) {
+					next();
+				}
+			}
+
+			// If the element already has the correct final state, delegate to
+			// the core methods so the internal tracking of "olddisplay" works.
+			if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
+				elem[ mode ]();
+				done();
+			} else {
+				effectMethod.call( elem[0], args, done );
+			}
+		}
+
+		return queue === false ? this.each( run ) : this.queue( queue || "fx", run );
+	},
+
+	show: (function( orig ) {
+		return function( option ) {
+			if ( standardAnimationOption( option ) ) {
+				return orig.apply( this, arguments );
+			} else {
+				var args = _normalizeArguments.apply( this, arguments );
+				args.mode = "show";
+				return this.effect.call( this, args );
+			}
+		};
+	})( $.fn.show ),
+
+	hide: (function( orig ) {
+		return function( option ) {
+			if ( standardAnimationOption( option ) ) {
+				return orig.apply( this, arguments );
+			} else {
+				var args = _normalizeArguments.apply( this, arguments );
+				args.mode = "hide";
+				return this.effect.call( this, args );
+			}
+		};
+	})( $.fn.hide ),
+
+	toggle: (function( orig ) {
+		return function( option ) {
+			if ( standardAnimationOption( option ) || typeof option === "boolean" ) {
+				return orig.apply( this, arguments );
+			} else {
+				var args = _normalizeArguments.apply( this, arguments );
+				args.mode = "toggle";
+				return this.effect.call( this, args );
+			}
+		};
+	})( $.fn.toggle ),
+
+	// helper functions
+	cssUnit: function(key) {
+		var style = this.css( key ),
+			val = [];
+
+		$.each( [ "em", "px", "%", "pt" ], function( i, unit ) {
+			if ( style.indexOf( unit ) > 0 ) {
+				val = [ parseFloat( style ), unit ];
+			}
+		});
+		return val;
+	}
+});
+
+})();
+
+/******************************************************************************/
+/*********************************** EASING ***********************************/
+/******************************************************************************/
+
+(function() {
+
+// based on easing equations from Robert Penner (http://www.robertpenner.com/easing)
+
+var baseEasings = {};
+
+$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) {
+	baseEasings[ name ] = function( p ) {
+		return Math.pow( p, i + 2 );
+	};
+});
+
+$.extend( baseEasings, {
+	Sine: function( p ) {
+		return 1 - Math.cos( p * Math.PI / 2 );
+	},
+	Circ: function( p ) {
+		return 1 - Math.sqrt( 1 - p * p );
+	},
+	Elastic: function( p ) {
+		return p === 0 || p === 1 ? p :
+			-Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 );
+	},
+	Back: function( p ) {
+		return p * p * ( 3 * p - 2 );
+	},
+	Bounce: function( p ) {
+		var pow2,
+			bounce = 4;
+
+		while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}
+		return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );
+	}
+});
+
+$.each( baseEasings, function( name, easeIn ) {
+	$.easing[ "easeIn" + name ] = easeIn;
+	$.easing[ "easeOut" + name ] = function( p ) {
+		return 1 - easeIn( 1 - p );
+	};
+	$.easing[ "easeInOut" + name ] = function( p ) {
+		return p < 0.5 ?
+			easeIn( p * 2 ) / 2 :
+			1 - easeIn( p * -2 + 2 ) / 2;
+	};
+});
+
+})();
+
+var effect = $.effects;
+
+
+/*!
+ * jQuery UI Effects Blind 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/blind-effect/
+ */
+
+
+var effectBlind = $.effects.effect.blind = function( o, done ) {
+	// Create element
+	var el = $( this ),
+		rvertical = /up|down|vertical/,
+		rpositivemotion = /up|left|vertical|horizontal/,
+		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+		mode = $.effects.setMode( el, o.mode || "hide" ),
+		direction = o.direction || "up",
+		vertical = rvertical.test( direction ),
+		ref = vertical ? "height" : "width",
+		ref2 = vertical ? "top" : "left",
+		motion = rpositivemotion.test( direction ),
+		animation = {},
+		show = mode === "show",
+		wrapper, distance, margin;
+
+	// if already wrapped, the wrapper's properties are my property. #6245
+	if ( el.parent().is( ".ui-effects-wrapper" ) ) {
+		$.effects.save( el.parent(), props );
+	} else {
+		$.effects.save( el, props );
+	}
+	el.show();
+	wrapper = $.effects.createWrapper( el ).css({
+		overflow: "hidden"
+	});
+
+	distance = wrapper[ ref ]();
+	margin = parseFloat( wrapper.css( ref2 ) ) || 0;
+
+	animation[ ref ] = show ? distance : 0;
+	if ( !motion ) {
+		el
+			.css( vertical ? "bottom" : "right", 0 )
+			.css( vertical ? "top" : "left", "auto" )
+			.css({ position: "absolute" });
+
+		animation[ ref2 ] = show ? margin : distance + margin;
+	}
+
+	// start at 0 if we are showing
+	if ( show ) {
+		wrapper.css( ref, 0 );
+		if ( !motion ) {
+			wrapper.css( ref2, margin + distance );
+		}
+	}
+
+	// Animate
+	wrapper.animate( animation, {
+		duration: o.duration,
+		easing: o.easing,
+		queue: false,
+		complete: function() {
+			if ( mode === "hide" ) {
+				el.hide();
+			}
+			$.effects.restore( el, props );
+			$.effects.removeWrapper( el );
+			done();
+		}
+	});
+};
+
+
+/*!
+ * jQuery UI Effects Bounce 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/bounce-effect/
+ */
+
+
+var effectBounce = $.effects.effect.bounce = function( o, done ) {
+	var el = $( this ),
+		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+
+		// defaults:
+		mode = $.effects.setMode( el, o.mode || "effect" ),
+		hide = mode === "hide",
+		show = mode === "show",
+		direction = o.direction || "up",
+		distance = o.distance,
+		times = o.times || 5,
+
+		// number of internal animations
+		anims = times * 2 + ( show || hide ? 1 : 0 ),
+		speed = o.duration / anims,
+		easing = o.easing,
+
+		// utility:
+		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
+		motion = ( direction === "up" || direction === "left" ),
+		i,
+		upAnim,
+		downAnim,
+
+		// we will need to re-assemble the queue to stack our animations in place
+		queue = el.queue(),
+		queuelen = queue.length;
+
+	// Avoid touching opacity to prevent clearType and PNG issues in IE
+	if ( show || hide ) {
+		props.push( "opacity" );
+	}
+
+	$.effects.save( el, props );
+	el.show();
+	$.effects.createWrapper( el ); // Create Wrapper
+
+	// default distance for the BIGGEST bounce is the outer Distance / 3
+	if ( !distance ) {
+		distance = el[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3;
+	}
+
+	if ( show ) {
+		downAnim = { opacity: 1 };
+		downAnim[ ref ] = 0;
+
+		// if we are showing, force opacity 0 and set the initial position
+		// then do the "first" animation
+		el.css( "opacity", 0 )
+			.css( ref, motion ? -distance * 2 : distance * 2 )
+			.animate( downAnim, speed, easing );
+	}
+
+	// start at the smallest distance if we are hiding
+	if ( hide ) {
+		distance = distance / Math.pow( 2, times - 1 );
+	}
+
+	downAnim = {};
+	downAnim[ ref ] = 0;
+	// Bounces up/down/left/right then back to 0 -- times * 2 animations happen here
+	for ( i = 0; i < times; i++ ) {
+		upAnim = {};
+		upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
+
+		el.animate( upAnim, speed, easing )
+			.animate( downAnim, speed, easing );
+
+		distance = hide ? distance * 2 : distance / 2;
+	}
+
+	// Last Bounce when Hiding
+	if ( hide ) {
+		upAnim = { opacity: 0 };
+		upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
+
+		el.animate( upAnim, speed, easing );
+	}
+
+	el.queue(function() {
+		if ( hide ) {
+			el.hide();
+		}
+		$.effects.restore( el, props );
+		$.effects.removeWrapper( el );
+		done();
+	});
+
+	// inject all the animations we just queued to be first in line (after "inprogress")
+	if ( queuelen > 1) {
+		queue.splice.apply( queue,
+			[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
+	}
+	el.dequeue();
+
+};
+
+
+/*!
+ * jQuery UI Effects Clip 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/clip-effect/
+ */
+
+
+var effectClip = $.effects.effect.clip = function( o, done ) {
+	// Create element
+	var el = $( this ),
+		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+		mode = $.effects.setMode( el, o.mode || "hide" ),
+		show = mode === "show",
+		direction = o.direction || "vertical",
+		vert = direction === "vertical",
+		size = vert ? "height" : "width",
+		position = vert ? "top" : "left",
+		animation = {},
+		wrapper, animate, distance;
+
+	// Save & Show
+	$.effects.save( el, props );
+	el.show();
+
+	// Create Wrapper
+	wrapper = $.effects.createWrapper( el ).css({
+		overflow: "hidden"
+	});
+	animate = ( el[0].tagName === "IMG" ) ? wrapper : el;
+	distance = animate[ size ]();
+
+	// Shift
+	if ( show ) {
+		animate.css( size, 0 );
+		animate.css( position, distance / 2 );
+	}
+
+	// Create Animation Object:
+	animation[ size ] = show ? distance : 0;
+	animation[ position ] = show ? 0 : distance / 2;
+
+	// Animate
+	animate.animate( animation, {
+		queue: false,
+		duration: o.duration,
+		easing: o.easing,
+		complete: function() {
+			if ( !show ) {
+				el.hide();
+			}
+			$.effects.restore( el, props );
+			$.effects.removeWrapper( el );
+			done();
+		}
+	});
+
+};
+
+
+/*!
+ * jQuery UI Effects Drop 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/drop-effect/
+ */
+
+
+var effectDrop = $.effects.effect.drop = function( o, done ) {
+
+	var el = $( this ),
+		props = [ "position", "top", "bottom", "left", "right", "opacity", "height", "width" ],
+		mode = $.effects.setMode( el, o.mode || "hide" ),
+		show = mode === "show",
+		direction = o.direction || "left",
+		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
+		motion = ( direction === "up" || direction === "left" ) ? "pos" : "neg",
+		animation = {
+			opacity: show ? 1 : 0
+		},
+		distance;
+
+	// Adjust
+	$.effects.save( el, props );
+	el.show();
+	$.effects.createWrapper( el );
+
+	distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ) / 2;
+
+	if ( show ) {
+		el
+			.css( "opacity", 0 )
+			.css( ref, motion === "pos" ? -distance : distance );
+	}
+
+	// Animation
+	animation[ ref ] = ( show ?
+		( motion === "pos" ? "+=" : "-=" ) :
+		( motion === "pos" ? "-=" : "+=" ) ) +
+		distance;
+
+	// Animate
+	el.animate( animation, {
+		queue: false,
+		duration: o.duration,
+		easing: o.easing,
+		complete: function() {
+			if ( mode === "hide" ) {
+				el.hide();
+			}
+			$.effects.restore( el, props );
+			$.effects.removeWrapper( el );
+			done();
+		}
+	});
+};
+
+
+/*!
+ * jQuery UI Effects Explode 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/explode-effect/
+ */
+
+
+var effectExplode = $.effects.effect.explode = function( o, done ) {
+
+	var rows = o.pieces ? Math.round( Math.sqrt( o.pieces ) ) : 3,
+		cells = rows,
+		el = $( this ),
+		mode = $.effects.setMode( el, o.mode || "hide" ),
+		show = mode === "show",
+
+		// show and then visibility:hidden the element before calculating offset
+		offset = el.show().css( "visibility", "hidden" ).offset(),
+
+		// width and height of a piece
+		width = Math.ceil( el.outerWidth() / cells ),
+		height = Math.ceil( el.outerHeight() / rows ),
+		pieces = [],
+
+		// loop
+		i, j, left, top, mx, my;
+
+	// children animate complete:
+	function childComplete() {
+		pieces.push( this );
+		if ( pieces.length === rows * cells ) {
+			animComplete();
+		}
+	}
+
+	// clone the element for each row and cell.
+	for ( i = 0; i < rows ; i++ ) { // ===>
+		top = offset.top + i * height;
+		my = i - ( rows - 1 ) / 2 ;
+
+		for ( j = 0; j < cells ; j++ ) { // |||
+			left = offset.left + j * width;
+			mx = j - ( cells - 1 ) / 2 ;
+
+			// Create a clone of the now hidden main element that will be absolute positioned
+			// within a wrapper div off the -left and -top equal to size of our pieces
+			el
+				.clone()
+				.appendTo( "body" )
+				.wrap( "<div></div>" )
+				.css({
+					position: "absolute",
+					visibility: "visible",
+					left: -j * width,
+					top: -i * height
+				})
+
+			// select the wrapper - make it overflow: hidden and absolute positioned based on
+			// where the original was located +left and +top equal to the size of pieces
+				.parent()
+				.addClass( "ui-effects-explode" )
+				.css({
+					position: "absolute",
+					overflow: "hidden",
+					width: width,
+					height: height,
+					left: left + ( show ? mx * width : 0 ),
+					top: top + ( show ? my * height : 0 ),
+					opacity: show ? 0 : 1
+				}).animate({
+					left: left + ( show ? 0 : mx * width ),
+					top: top + ( show ? 0 : my * height ),
+					opacity: show ? 1 : 0
+				}, o.duration || 500, o.easing, childComplete );
+		}
+	}
+
+	function animComplete() {
+		el.css({
+			visibility: "visible"
+		});
+		$( pieces ).remove();
+		if ( !show ) {
+			el.hide();
+		}
+		done();
+	}
+};
+
+
+/*!
+ * jQuery UI Effects Fade 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/fade-effect/
+ */
+
+
+var effectFade = $.effects.effect.fade = function( o, done ) {
+	var el = $( this ),
+		mode = $.effects.setMode( el, o.mode || "toggle" );
+
+	el.animate({
+		opacity: mode
+	}, {
+		queue: false,
+		duration: o.duration,
+		easing: o.easing,
+		complete: done
+	});
+};
+
+
+/*!
+ * jQuery UI Effects Fold 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/fold-effect/
+ */
+
+
+var effectFold = $.effects.effect.fold = function( o, done ) {
+
+	// Create element
+	var el = $( this ),
+		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+		mode = $.effects.setMode( el, o.mode || "hide" ),
+		show = mode === "show",
+		hide = mode === "hide",
+		size = o.size || 15,
+		percent = /([0-9]+)%/.exec( size ),
+		horizFirst = !!o.horizFirst,
+		widthFirst = show !== horizFirst,
+		ref = widthFirst ? [ "width", "height" ] : [ "height", "width" ],
+		duration = o.duration / 2,
+		wrapper, distance,
+		animation1 = {},
+		animation2 = {};
+
+	$.effects.save( el, props );
+	el.show();
+
+	// Create Wrapper
+	wrapper = $.effects.createWrapper( el ).css({
+		overflow: "hidden"
+	});
+	distance = widthFirst ?
+		[ wrapper.width(), wrapper.height() ] :
+		[ wrapper.height(), wrapper.width() ];
+
+	if ( percent ) {
+		size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ];
+	}
+	if ( show ) {
+		wrapper.css( horizFirst ? {
+			height: 0,
+			width: size
+		} : {
+			height: size,
+			width: 0
+		});
+	}
+
+	// Animation
+	animation1[ ref[ 0 ] ] = show ? distance[ 0 ] : size;
+	animation2[ ref[ 1 ] ] = show ? distance[ 1 ] : 0;
+
+	// Animate
+	wrapper
+		.animate( animation1, duration, o.easing )
+		.animate( animation2, duration, o.easing, function() {
+			if ( hide ) {
+				el.hide();
+			}
+			$.effects.restore( el, props );
+			$.effects.removeWrapper( el );
+			done();
+		});
+
+};
+
+
+/*!
+ * jQuery UI Effects Highlight 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/highlight-effect/
+ */
+
+
+var effectHighlight = $.effects.effect.highlight = function( o, done ) {
+	var elem = $( this ),
+		props = [ "backgroundImage", "backgroundColor", "opacity" ],
+		mode = $.effects.setMode( elem, o.mode || "show" ),
+		animation = {
+			backgroundColor: elem.css( "backgroundColor" )
+		};
+
+	if (mode === "hide") {
+		animation.opacity = 0;
+	}
+
+	$.effects.save( elem, props );
+
+	elem
+		.show()
+		.css({
+			backgroundImage: "none",
+			backgroundColor: o.color || "#ffff99"
+		})
+		.animate( animation, {
+			queue: false,
+			duration: o.duration,
+			easing: o.easing,
+			complete: function() {
+				if ( mode === "hide" ) {
+					elem.hide();
+				}
+				$.effects.restore( elem, props );
+				done();
+			}
+		});
+};
+
+
+/*!
+ * jQuery UI Effects Size 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/size-effect/
+ */
+
+
+var effectSize = $.effects.effect.size = function( o, done ) {
+
+	// Create element
+	var original, baseline, factor,
+		el = $( this ),
+		props0 = [ "position", "top", "bottom", "left", "right", "width", "height", "overflow", "opacity" ],
+
+		// Always restore
+		props1 = [ "position", "top", "bottom", "left", "right", "overflow", "opacity" ],
+
+		// Copy for children
+		props2 = [ "width", "height", "overflow" ],
+		cProps = [ "fontSize" ],
+		vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ],
+		hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ],
+
+		// Set options
+		mode = $.effects.setMode( el, o.mode || "effect" ),
+		restore = o.restore || mode !== "effect",
+		scale = o.scale || "both",
+		origin = o.origin || [ "middle", "center" ],
+		position = el.css( "position" ),
+		props = restore ? props0 : props1,
+		zero = {
+			height: 0,
+			width: 0,
+			outerHeight: 0,
+			outerWidth: 0
+		};
+
+	if ( mode === "show" ) {
+		el.show();
+	}
+	original = {
+		height: el.height(),
+		width: el.width(),
+		outerHeight: el.outerHeight(),
+		outerWidth: el.outerWidth()
+	};
+
+	if ( o.mode === "toggle" && mode === "show" ) {
+		el.from = o.to || zero;
+		el.to = o.from || original;
+	} else {
+		el.from = o.from || ( mode === "show" ? zero : original );
+		el.to = o.to || ( mode === "hide" ? zero : original );
+	}
+
+	// Set scaling factor
+	factor = {
+		from: {
+			y: el.from.height / original.height,
+			x: el.from.width / original.width
+		},
+		to: {
+			y: el.to.height / original.height,
+			x: el.to.width / original.width
+		}
+	};
+
+	// Scale the css box
+	if ( scale === "box" || scale === "both" ) {
+
+		// Vertical props scaling
+		if ( factor.from.y !== factor.to.y ) {
+			props = props.concat( vProps );
+			el.from = $.effects.setTransition( el, vProps, factor.from.y, el.from );
+			el.to = $.effects.setTransition( el, vProps, factor.to.y, el.to );
+		}
+
+		// Horizontal props scaling
+		if ( factor.from.x !== factor.to.x ) {
+			props = props.concat( hProps );
+			el.from = $.effects.setTransition( el, hProps, factor.from.x, el.from );
+			el.to = $.effects.setTransition( el, hProps, factor.to.x, el.to );
+		}
+	}
+
+	// Scale the content
+	if ( scale === "content" || scale === "both" ) {
+
+		// Vertical props scaling
+		if ( factor.from.y !== factor.to.y ) {
+			props = props.concat( cProps ).concat( props2 );
+			el.from = $.effects.setTransition( el, cProps, factor.from.y, el.from );
+			el.to = $.effects.setTransition( el, cProps, factor.to.y, el.to );
+		}
+	}
+
+	$.effects.save( el, props );
+	el.show();
+	$.effects.createWrapper( el );
+	el.css( "overflow", "hidden" ).css( el.from );
+
+	// Adjust
+	if (origin) { // Calculate baseline shifts
+		baseline = $.effects.getBaseline( origin, original );
+		el.from.top = ( original.outerHeight - el.outerHeight() ) * baseline.y;
+		el.from.left = ( original.outerWidth - el.outerWidth() ) * baseline.x;
+		el.to.top = ( original.outerHeight - el.to.outerHeight ) * baseline.y;
+		el.to.left = ( original.outerWidth - el.to.outerWidth ) * baseline.x;
+	}
+	el.css( el.from ); // set top & left
+
+	// Animate
+	if ( scale === "content" || scale === "both" ) { // Scale the children
+
+		// Add margins/font-size
+		vProps = vProps.concat([ "marginTop", "marginBottom" ]).concat(cProps);
+		hProps = hProps.concat([ "marginLeft", "marginRight" ]);
+		props2 = props0.concat(vProps).concat(hProps);
+
+		el.find( "*[width]" ).each( function() {
+			var child = $( this ),
+				c_original = {
+					height: child.height(),
+					width: child.width(),
+					outerHeight: child.outerHeight(),
+					outerWidth: child.outerWidth()
+				};
+			if (restore) {
+				$.effects.save(child, props2);
+			}
+
+			child.from = {
+				height: c_original.height * factor.from.y,
+				width: c_original.width * factor.from.x,
+				outerHeight: c_original.outerHeight * factor.from.y,
+				outerWidth: c_original.outerWidth * factor.from.x
+			};
+			child.to = {
+				height: c_original.height * factor.to.y,
+				width: c_original.width * factor.to.x,
+				outerHeight: c_original.height * factor.to.y,
+				outerWidth: c_original.width * factor.to.x
+			};
+
+			// Vertical props scaling
+			if ( factor.from.y !== factor.to.y ) {
+				child.from = $.effects.setTransition( child, vProps, factor.from.y, child.from );
+				child.to = $.effects.setTransition( child, vProps, factor.to.y, child.to );
+			}
+
+			// Horizontal props scaling
+			if ( factor.from.x !== factor.to.x ) {
+				child.from = $.effects.setTransition( child, hProps, factor.from.x, child.from );
+				child.to = $.effects.setTransition( child, hProps, factor.to.x, child.to );
+			}
+
+			// Animate children
+			child.css( child.from );
+			child.animate( child.to, o.duration, o.easing, function() {
+
+				// Restore children
+				if ( restore ) {
+					$.effects.restore( child, props2 );
+				}
+			});
+		});
+	}
+
+	// Animate
+	el.animate( el.to, {
+		queue: false,
+		duration: o.duration,
+		easing: o.easing,
+		complete: function() {
+			if ( el.to.opacity === 0 ) {
+				el.css( "opacity", el.from.opacity );
+			}
+			if ( mode === "hide" ) {
+				el.hide();
+			}
+			$.effects.restore( el, props );
+			if ( !restore ) {
+
+				// we need to calculate our new positioning based on the scaling
+				if ( position === "static" ) {
+					el.css({
+						position: "relative",
+						top: el.to.top,
+						left: el.to.left
+					});
+				} else {
+					$.each([ "top", "left" ], function( idx, pos ) {
+						el.css( pos, function( _, str ) {
+							var val = parseInt( str, 10 ),
+								toRef = idx ? el.to.left : el.to.top;
+
+							// if original was "auto", recalculate the new value from wrapper
+							if ( str === "auto" ) {
+								return toRef + "px";
+							}
+
+							return val + toRef + "px";
+						});
+					});
+				}
+			}
+
+			$.effects.removeWrapper( el );
+			done();
+		}
+	});
+
+};
+
+
+/*!
+ * jQuery UI Effects Scale 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/scale-effect/
+ */
+
+
+var effectScale = $.effects.effect.scale = function( o, done ) {
+
+	// Create element
+	var el = $( this ),
+		options = $.extend( true, {}, o ),
+		mode = $.effects.setMode( el, o.mode || "effect" ),
+		percent = parseInt( o.percent, 10 ) ||
+			( parseInt( o.percent, 10 ) === 0 ? 0 : ( mode === "hide" ? 0 : 100 ) ),
+		direction = o.direction || "both",
+		origin = o.origin,
+		original = {
+			height: el.height(),
+			width: el.width(),
+			outerHeight: el.outerHeight(),
+			outerWidth: el.outerWidth()
+		},
+		factor = {
+			y: direction !== "horizontal" ? (percent / 100) : 1,
+			x: direction !== "vertical" ? (percent / 100) : 1
+		};
+
+	// We are going to pass this effect to the size effect:
+	options.effect = "size";
+	options.queue = false;
+	options.complete = done;
+
+	// Set default origin and restore for show/hide
+	if ( mode !== "effect" ) {
+		options.origin = origin || [ "middle", "center" ];
+		options.restore = true;
+	}
+
+	options.from = o.from || ( mode === "show" ? {
+		height: 0,
+		width: 0,
+		outerHeight: 0,
+		outerWidth: 0
+	} : original );
+	options.to = {
+		height: original.height * factor.y,
+		width: original.width * factor.x,
+		outerHeight: original.outerHeight * factor.y,
+		outerWidth: original.outerWidth * factor.x
+	};
+
+	// Fade option to support puff
+	if ( options.fade ) {
+		if ( mode === "show" ) {
+			options.from.opacity = 0;
+			options.to.opacity = 1;
+		}
+		if ( mode === "hide" ) {
+			options.from.opacity = 1;
+			options.to.opacity = 0;
+		}
+	}
+
+	// Animate
+	el.effect( options );
+
+};
+
+
+/*!
+ * jQuery UI Effects Puff 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/puff-effect/
+ */
+
+
+var effectPuff = $.effects.effect.puff = function( o, done ) {
+	var elem = $( this ),
+		mode = $.effects.setMode( elem, o.mode || "hide" ),
+		hide = mode === "hide",
+		percent = parseInt( o.percent, 10 ) || 150,
+		factor = percent / 100,
+		original = {
+			height: elem.height(),
+			width: elem.width(),
+			outerHeight: elem.outerHeight(),
+			outerWidth: elem.outerWidth()
+		};
+
+	$.extend( o, {
+		effect: "scale",
+		queue: false,
+		fade: true,
+		mode: mode,
+		complete: done,
+		percent: hide ? percent : 100,
+		from: hide ?
+			original :
+			{
+				height: original.height * factor,
+				width: original.width * factor,
+				outerHeight: original.outerHeight * factor,
+				outerWidth: original.outerWidth * factor
+			}
+	});
+
+	elem.effect( o );
+};
+
+
+/*!
+ * jQuery UI Effects Pulsate 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/pulsate-effect/
+ */
+
+
+var effectPulsate = $.effects.effect.pulsate = function( o, done ) {
+	var elem = $( this ),
+		mode = $.effects.setMode( elem, o.mode || "show" ),
+		show = mode === "show",
+		hide = mode === "hide",
+		showhide = ( show || mode === "hide" ),
+
+		// showing or hiding leaves of the "last" animation
+		anims = ( ( o.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ),
+		duration = o.duration / anims,
+		animateTo = 0,
+		queue = elem.queue(),
+		queuelen = queue.length,
+		i;
+
+	if ( show || !elem.is(":visible")) {
+		elem.css( "opacity", 0 ).show();
+		animateTo = 1;
+	}
+
+	// anims - 1 opacity "toggles"
+	for ( i = 1; i < anims; i++ ) {
+		elem.animate({
+			opacity: animateTo
+		}, duration, o.easing );
+		animateTo = 1 - animateTo;
+	}
+
+	elem.animate({
+		opacity: animateTo
+	}, duration, o.easing);
+
+	elem.queue(function() {
+		if ( hide ) {
+			elem.hide();
+		}
+		done();
+	});
+
+	// We just queued up "anims" animations, we need to put them next in the queue
+	if ( queuelen > 1 ) {
+		queue.splice.apply( queue,
+			[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
+	}
+	elem.dequeue();
+};
+
+
+/*!
+ * jQuery UI Effects Shake 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/shake-effect/
+ */
+
+
+var effectShake = $.effects.effect.shake = function( o, done ) {
+
+	var el = $( this ),
+		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+		mode = $.effects.setMode( el, o.mode || "effect" ),
+		direction = o.direction || "left",
+		distance = o.distance || 20,
+		times = o.times || 3,
+		anims = times * 2 + 1,
+		speed = Math.round( o.duration / anims ),
+		ref = (direction === "up" || direction === "down") ? "top" : "left",
+		positiveMotion = (direction === "up" || direction === "left"),
+		animation = {},
+		animation1 = {},
+		animation2 = {},
+		i,
+
+		// we will need to re-assemble the queue to stack our animations in place
+		queue = el.queue(),
+		queuelen = queue.length;
+
+	$.effects.save( el, props );
+	el.show();
+	$.effects.createWrapper( el );
+
+	// Animation
+	animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance;
+	animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2;
+	animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2;
+
+	// Animate
+	el.animate( animation, speed, o.easing );
+
+	// Shakes
+	for ( i = 1; i < times; i++ ) {
+		el.animate( animation1, speed, o.easing ).animate( animation2, speed, o.easing );
+	}
+	el
+		.animate( animation1, speed, o.easing )
+		.animate( animation, speed / 2, o.easing )
+		.queue(function() {
+			if ( mode === "hide" ) {
+				el.hide();
+			}
+			$.effects.restore( el, props );
+			$.effects.removeWrapper( el );
+			done();
+		});
+
+	// inject all the animations we just queued to be first in line (after "inprogress")
+	if ( queuelen > 1) {
+		queue.splice.apply( queue,
+			[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
+	}
+	el.dequeue();
+
+};
+
+
+/*!
+ * jQuery UI Effects Slide 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/slide-effect/
+ */
+
+
+var effectSlide = $.effects.effect.slide = function( o, done ) {
+
+	// Create element
+	var el = $( this ),
+		props = [ "position", "top", "bottom", "left", "right", "width", "height" ],
+		mode = $.effects.setMode( el, o.mode || "show" ),
+		show = mode === "show",
+		direction = o.direction || "left",
+		ref = (direction === "up" || direction === "down") ? "top" : "left",
+		positiveMotion = (direction === "up" || direction === "left"),
+		distance,
+		animation = {};
+
+	// Adjust
+	$.effects.save( el, props );
+	el.show();
+	distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true );
+
+	$.effects.createWrapper( el ).css({
+		overflow: "hidden"
+	});
+
+	if ( show ) {
+		el.css( ref, positiveMotion ? (isNaN(distance) ? "-" + distance : -distance) : distance );
+	}
+
+	// Animation
+	animation[ ref ] = ( show ?
+		( positiveMotion ? "+=" : "-=") :
+		( positiveMotion ? "-=" : "+=")) +
+		distance;
+
+	// Animate
+	el.animate( animation, {
+		queue: false,
+		duration: o.duration,
+		easing: o.easing,
+		complete: function() {
+			if ( mode === "hide" ) {
+				el.hide();
+			}
+			$.effects.restore( el, props );
+			$.effects.removeWrapper( el );
+			done();
+		}
+	});
+};
+
+
+/*!
+ * jQuery UI Effects Transfer 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/transfer-effect/
+ */
+
+
+var effectTransfer = $.effects.effect.transfer = function( o, done ) {
+	var elem = $( this ),
+		target = $( o.to ),
+		targetFixed = target.css( "position" ) === "fixed",
+		body = $("body"),
+		fixTop = targetFixed ? body.scrollTop() : 0,
+		fixLeft = targetFixed ? body.scrollLeft() : 0,
+		endPosition = target.offset(),
+		animation = {
+			top: endPosition.top - fixTop,
+			left: endPosition.left - fixLeft,
+			height: target.innerHeight(),
+			width: target.innerWidth()
+		},
+		startPosition = elem.offset(),
+		transfer = $( "<div class='ui-effects-transfer'></div>" )
+			.appendTo( document.body )
+			.addClass( o.className )
+			.css({
+				top: startPosition.top - fixTop,
+				left: startPosition.left - fixLeft,
+				height: elem.innerHeight(),
+				width: elem.innerWidth(),
+				position: targetFixed ? "fixed" : "absolute"
+			})
+			.animate( animation, o.duration, o.easing, function() {
+				transfer.remove();
+				done();
+			});
+};
+
+
+
+}));
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.min.css
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.min.css	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.min.css	(revision 2119)
@@ -0,0 +1,7 @@
+/*! jQuery UI - v1.11.4 - 2015-04-09
+* http://jqueryui.com
+* Includes: core.css, draggable.css, resizable.css, selectable.css, sortable.css, accordion.css, autocomplete.css, button.css, datepicker.css, dialog.css, menu.css, progressbar.css, selectmenu.css, slider.css, spinner.css, tabs.css, tooltip.css, theme.css
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=2191c0&bgTextureHeader=gloss_wave&bgImgOpacityHeader=75&borderColorHeader=4297d7&fcHeader=eaf5f7&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=inset_hard&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=0078ae&bgColorDefault=0078ae&bgTextureDefault=glass&bgImgOpacityDefault=45&borderColorDefault=77d5f7&fcDefault=ffffff&iconColorDefault=e0fdff&bgColorHover=79c9ec&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=448dae&fcHover=026890&iconColorHover=056b93&bgColorActive=6eac2c&bgTextureActive=gloss_wave&bgImgOpacityActive=50&borderColorActive=acdd4a&fcActive=ffffff&iconColorActive=f5e175&bgColorHighlight=f8da4e&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcd113&fcHighlight=915608&iconColorHighlight=f7a50d&bgColorError=e14f1c&bgTextureError=gloss_wave&bgImgOpacityError=45&borderColorError=cd0a0a&fcError=ffffff&iconColorError=fcd113&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=75&opacityOverlay=30&bgColorShadow=999999&bgTextureShadow=flat&bgImgOpacityShadow=55&opacityShadow=45&thicknessShadow=0px&offsetTopShadow=5px&offsetLeftShadow=5px&cornerRadiusShadow=5px
+* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
+
+.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin:2px 0 0 0;padding:.5em .5em .5em .7em;min-height:0;font-size:100%}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{overflow:hidden;position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:none}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{position:relative;margin:0;padding:3px 1em 3px .4em;cursor:pointer;min-height:0;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:bold;line-height:1.5;padding:2px 0.4em;margin:0.5em 0 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-button{display:inline-block;overflow:hidden;position:relative;text-decoration:none;cursor:pointer}.ui-selectmenu-button span.ui-icon{right:0.5em;left:auto;margin-top:-8px;position:absolute;top:50%}.ui-selectmenu-button span.ui-selectmenu-text{text-align:left;padding:0.4em 2.1em 0.4em 1em;display:block;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #a6c9e2;background:#fcfdfd url("images/ui-bg_inset-hard_100_fcfdfd_1x100.png") 50% bottom repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #4297d7;background:#2191c0 url("images/ui-bg_gloss-wave_75_2191c0_500x100.png") 50% 50% repeat-x;color:#eaf5f7;font-weight:bold}.ui-widget-header a{color:#eaf5f7}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #77d5f7;background:#0078ae url("images/ui-bg_glass_45_0078ae_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#fff}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#fff;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #448dae;background:#79c9ec url("images/ui-bg_glass_75_79c9ec_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#026890}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#026890;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #acdd4a;background:#6eac2c url("images/ui-bg_gloss-wave_50_6eac2c_500x100.png") 50% 50% repeat-x;font-weight:normal;color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcd113;background:#f8da4e url("images/ui-bg_glass_55_f8da4e_1x400.png") 50% 50% repeat-x;color:#915608}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#915608}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#e14f1c url("images/ui-bg_gloss-wave_45_e14f1c_500x100.png") 50% top repeat-x;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_0078ae_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_d8e7f3_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_e0fdff_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_056b93_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_f5e175_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_f7a50d_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_fcd113_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:5px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:5px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:5px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:5px}.ui-widget-overlay{background:#aaa url("images/ui-bg_flat_75_aaaaaa_40x100.png") 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:5px 0 0 5px;padding:0;background:#999 url("images/ui-bg_flat_55_999999_40x100.png") 50% 50% repeat-x;opacity:.45;filter:Alpha(Opacity=45);border-radius:5px}
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.min.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.min.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.min.js	(revision 2119)
@@ -0,0 +1,13 @@
+/*! jQuery UI - v1.11.4 - 2015-04-05
+* http://jqueryui.com
+* Includes: core.js, widget.js, mouse.js, position.js, draggable.js, droppable.js, resizable.js, selectable.js, sortable.js, accordion.js, autocomplete.js, button.js, datepicker.js, dialog.js, menu.js, progressbar.js, selectmenu.js, slider.js, spinner.js, tabs.js, tooltip.js, effect.js, effect-blind.js, effect-bounce.js, effect-clip.js, effect-drop.js, effect-explode.js, effect-fade.js, effect-fold.js, effect-highlight.js, effect-puff.js, effect-pulsate.js, effect-scale.js, effect-shake.js, effect-size.js, effect-slide.js, effect-transfer.js
+* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
+
+(function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e(jQuery)})(function(e){function t(t,s){var n,a,o,r=t.nodeName.toLowerCase();return"area"===r?(n=t.parentNode,a=n.name,t.href&&a&&"map"===n.nodeName.toLowerCase()?(o=e("img[usemap='#"+a+"']")[0],!!o&&i(o)):!1):(/^(input|select|textarea|button|object)$/.test(r)?!t.disabled:"a"===r?t.href||s:s)&&i(t)}function i(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}function s(e){for(var t,i;e.length&&e[0]!==document;){if(t=e.css("position"),("absolute"===t||"relative"===t||"fixed"===t)&&(i=parseInt(e.css("zIndex"),10),!isNaN(i)&&0!==i))return i;e=e.parent()}return 0}function n(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},e.extend(this._defaults,this.regional[""]),this.regional.en=e.extend(!0,{},this.regional[""]),this.regional["en-US"]=e.extend(!0,{},this.regional.en),this.dpDiv=a(e("<div id='"+this._mainDivId+"' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>"))}function a(t){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return t.delegate(i,"mouseout",function(){e(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&e(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&e(this).removeClass("ui-datepicker-next-hover")}).delegate(i,"mouseover",o)}function o(){e.datepicker._isDisabledDatepicker(v.inline?v.dpDiv.parent()[0]:v.input[0])||(e(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),e(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&e(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&e(this).addClass("ui-datepicker-next-hover"))}function r(t,i){e.extend(t,i);for(var s in i)null==i[s]&&(t[s]=i[s]);return t}function h(e){return function(){var t=this.element.val();e.apply(this,arguments),this._refresh(),t!==this.element.val()&&this._trigger("change")}}e.ui=e.ui||{},e.extend(e.ui,{version:"1.11.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({scrollParent:function(t){var i=this.css("position"),s="absolute"===i,n=t?/(auto|scroll|hidden)/:/(auto|scroll)/,a=this.parents().filter(function(){var t=e(this);return s&&"static"===t.css("position")?!1:n.test(t.css("overflow")+t.css("overflow-y")+t.css("overflow-x"))}).eq(0);return"fixed"!==i&&a.length?a:e(this[0].ownerDocument||document)},uniqueId:function(){var e=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++e)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,s){return!!e.data(t,s[3])},focusable:function(i){return t(i,!isNaN(e.attr(i,"tabindex")))},tabbable:function(i){var s=e.attr(i,"tabindex"),n=isNaN(s);return(n||s>=0)&&t(i,!n)}}),e("<a>").outerWidth(1).jquery||e.each(["Width","Height"],function(t,i){function s(t,i,s,a){return e.each(n,function(){i-=parseFloat(e.css(t,"padding"+this))||0,s&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),a&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],a=i.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+i]=function(t){return void 0===t?o["inner"+i].call(this):this.each(function(){e(this).css(a,s(this,t)+"px")})},e.fn["outer"+i]=function(t,n){return"number"!=typeof t?o["outer"+i].call(this,t):this.each(function(){e(this).css(a,s(this,t,!0,n)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("<a>").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.fn.extend({focus:function(t){return function(i,s){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),s&&s.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),disableSelection:function(){var e="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.bind(e+".ui-disableSelection",function(e){e.preventDefault()})}}(),enableSelection:function(){return this.unbind(".ui-disableSelection")},zIndex:function(t){if(void 0!==t)return this.css("zIndex",t);if(this.length)for(var i,s,n=e(this[0]);n.length&&n[0]!==document;){if(i=n.css("position"),("absolute"===i||"relative"===i||"fixed"===i)&&(s=parseInt(n.css("zIndex"),10),!isNaN(s)&&0!==s))return s;n=n.parent()}return 0}}),e.ui.plugin={add:function(t,i,s){var n,a=e.ui[t].prototype;for(n in s)a.plugins[n]=a.plugins[n]||[],a.plugins[n].push([i,s[n]])},call:function(e,t,i,s){var n,a=e.plugins[t];if(a&&(s||e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType))for(n=0;a.length>n;n++)e.options[a[n][0]]&&a[n][1].apply(e.element,i)}};var l=0,u=Array.prototype.slice;e.cleanData=function(t){return function(i){var s,n,a;for(a=0;null!=(n=i[a]);a++)try{s=e._data(n,"events"),s&&s.remove&&e(n).triggerHandler("remove")}catch(o){}t(i)}}(e.cleanData),e.widget=function(t,i,s){var n,a,o,r,h={},l=t.split(".")[0];return t=t.split(".")[1],n=l+"-"+t,s||(s=i,i=e.Widget),e.expr[":"][n.toLowerCase()]=function(t){return!!e.data(t,n)},e[l]=e[l]||{},a=e[l][t],o=e[l][t]=function(e,t){return this._createWidget?(arguments.length&&this._createWidget(e,t),void 0):new o(e,t)},e.extend(o,a,{version:s.version,_proto:e.extend({},s),_childConstructors:[]}),r=new i,r.options=e.widget.extend({},r.options),e.each(s,function(t,s){return e.isFunction(s)?(h[t]=function(){var e=function(){return i.prototype[t].apply(this,arguments)},n=function(e){return i.prototype[t].apply(this,e)};return function(){var t,i=this._super,a=this._superApply;return this._super=e,this._superApply=n,t=s.apply(this,arguments),this._super=i,this._superApply=a,t}}(),void 0):(h[t]=s,void 0)}),o.prototype=e.widget.extend(r,{widgetEventPrefix:a?r.widgetEventPrefix||t:t},h,{constructor:o,namespace:l,widgetName:t,widgetFullName:n}),a?(e.each(a._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete a._childConstructors):i._childConstructors.push(o),e.widget.bridge(t,o),o},e.widget.extend=function(t){for(var i,s,n=u.call(arguments,1),a=0,o=n.length;o>a;a++)for(i in n[a])s=n[a][i],n[a].hasOwnProperty(i)&&void 0!==s&&(t[i]=e.isPlainObject(s)?e.isPlainObject(t[i])?e.widget.extend({},t[i],s):e.widget.extend({},s):s);return t},e.widget.bridge=function(t,i){var s=i.prototype.widgetFullName||t;e.fn[t]=function(n){var a="string"==typeof n,o=u.call(arguments,1),r=this;return a?this.each(function(){var i,a=e.data(this,s);return"instance"===n?(r=a,!1):a?e.isFunction(a[n])&&"_"!==n.charAt(0)?(i=a[n].apply(a,o),i!==a&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):e.error("no such method '"+n+"' for "+t+" widget instance"):e.error("cannot call methods on "+t+" prior to initialization; "+"attempted to call method '"+n+"'")}):(o.length&&(n=e.widget.extend.apply(null,[n].concat(o))),this.each(function(){var t=e.data(this,s);t?(t.option(n||{}),t._init&&t._init()):e.data(this,s,new i(n,this))})),r}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{disabled:!1,create:null},_createWidget:function(t,i){i=e(i||this.defaultElement||this)[0],this.element=e(i),this.uuid=l++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=e(),this.hoverable=e(),this.focusable=e(),i!==this&&(e.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===i&&this.destroy()}}),this.document=e(i.style?i.ownerDocument:i.document||i),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(t,i){var s,n,a,o=t;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof t)if(o={},s=t.split("."),t=s.shift(),s.length){for(n=o[t]=e.widget.extend({},this.options[t]),a=0;s.length-1>a;a++)n[s[a]]=n[s[a]]||{},n=n[s[a]];if(t=s.pop(),1===arguments.length)return void 0===n[t]?null:n[t];n[t]=i}else{if(1===arguments.length)return void 0===this.options[t]?null:this.options[t];o[t]=i}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled",!!t),t&&(this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus"))),this},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_on:function(t,i,s){var n,a=this;"boolean"!=typeof t&&(s=i,i=t,t=!1),s?(i=n=e(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),e.each(s,function(s,o){function r(){return t||a.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?a[o]:o).apply(a,arguments):void 0}"string"!=typeof o&&(r.guid=o.guid=o.guid||r.guid||e.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+a.eventNamespace,u=h[2];u?n.delegate(u,l,r):i.bind(l,r)})},_off:function(t,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(i).undelegate(i),this.bindings=e(this.bindings.not(t).get()),this.focusable=e(this.focusable.not(t).get()),this.hoverable=e(this.hoverable.not(t).get())},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var n,a,o=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],a=i.originalEvent)for(n in a)n in i||(i[n]=a[n]);return this.element.trigger(i,s),!(e.isFunction(o)&&o.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,n,a){"string"==typeof n&&(n={effect:n});var o,r=n?n===!0||"number"==typeof n?i:n.effect||i:t;n=n||{},"number"==typeof n&&(n={duration:n}),o=!e.isEmptyObject(n),n.complete=a,n.delay&&s.delay(n.delay),o&&e.effects&&e.effects.effect[r]?s[t](n):r!==t&&s[r]?s[r](n.duration,n.easing,a):s.queue(function(i){e(this)[t](),a&&a.call(s[0]),i()})}}),e.widget;var d=!1;e(document).mouseup(function(){d=!1}),e.widget("ui.mouse",{version:"1.11.4",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(i){return!0===e.data(i.target,t.widgetName+".preventClickEvent")?(e.removeData(i.target,t.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&this.document.unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(!d){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var i=this,s=1===t.which,n="string"==typeof this.options.cancel&&t.target.nodeName?e(t.target).closest(this.options.cancel).length:!1;return s&&!n&&this._mouseCapture(t)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(t)!==!1,!this._mouseStarted)?(t.preventDefault(),!0):(!0===e.data(t.target,this.widgetName+".preventClickEvent")&&e.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return i._mouseMove(e)},this._mouseUpDelegate=function(e){return i._mouseUp(e)},this.document.bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),d=!0,!0)):!0}},_mouseMove:function(t){if(this._mouseMoved){if(e.ui.ie&&(!document.documentMode||9>document.documentMode)&&!t.button)return this._mouseUp(t);if(!t.which)return this._mouseUp(t)}return(t.which||t.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return this.document.unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),d=!1,!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),function(){function t(e,t,i){return[parseFloat(e[0])*(p.test(e[0])?t/100:1),parseFloat(e[1])*(p.test(e[1])?i/100:1)]}function i(t,i){return parseInt(e.css(t,i),10)||0}function s(t){var i=t[0];return 9===i.nodeType?{width:t.width(),height:t.height(),offset:{top:0,left:0}}:e.isWindow(i)?{width:t.width(),height:t.height(),offset:{top:t.scrollTop(),left:t.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:t.outerWidth(),height:t.outerHeight(),offset:t.offset()}}e.ui=e.ui||{};var n,a,o=Math.max,r=Math.abs,h=Math.round,l=/left|center|right/,u=/top|center|bottom/,d=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,p=/%$/,f=e.fn.position;e.position={scrollbarWidth:function(){if(void 0!==n)return n;var t,i,s=e("<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),a=s.children()[0];return e("body").append(s),t=a.offsetWidth,s.css("overflow","scroll"),i=a.offsetWidth,t===i&&(i=s[0].clientWidth),s.remove(),n=t-i},getScrollInfo:function(t){var i=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),s=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),n="scroll"===i||"auto"===i&&t.width<t.element[0].scrollWidth,a="scroll"===s||"auto"===s&&t.height<t.element[0].scrollHeight;return{width:a?e.position.scrollbarWidth():0,height:n?e.position.scrollbarWidth():0}},getWithinInfo:function(t){var i=e(t||window),s=e.isWindow(i[0]),n=!!i[0]&&9===i[0].nodeType;return{element:i,isWindow:s,isDocument:n,offset:i.offset()||{left:0,top:0},scrollLeft:i.scrollLeft(),scrollTop:i.scrollTop(),width:s||n?i.width():i.outerWidth(),height:s||n?i.height():i.outerHeight()}}},e.fn.position=function(n){if(!n||!n.of)return f.apply(this,arguments);n=e.extend({},n);var p,m,g,v,y,b,_=e(n.of),x=e.position.getWithinInfo(n.within),w=e.position.getScrollInfo(x),k=(n.collision||"flip").split(" "),T={};return b=s(_),_[0].preventDefault&&(n.at="left top"),m=b.width,g=b.height,v=b.offset,y=e.extend({},v),e.each(["my","at"],function(){var e,t,i=(n[this]||"").split(" ");1===i.length&&(i=l.test(i[0])?i.concat(["center"]):u.test(i[0])?["center"].concat(i):["center","center"]),i[0]=l.test(i[0])?i[0]:"center",i[1]=u.test(i[1])?i[1]:"center",e=d.exec(i[0]),t=d.exec(i[1]),T[this]=[e?e[0]:0,t?t[0]:0],n[this]=[c.exec(i[0])[0],c.exec(i[1])[0]]}),1===k.length&&(k[1]=k[0]),"right"===n.at[0]?y.left+=m:"center"===n.at[0]&&(y.left+=m/2),"bottom"===n.at[1]?y.top+=g:"center"===n.at[1]&&(y.top+=g/2),p=t(T.at,m,g),y.left+=p[0],y.top+=p[1],this.each(function(){var s,l,u=e(this),d=u.outerWidth(),c=u.outerHeight(),f=i(this,"marginLeft"),b=i(this,"marginTop"),D=d+f+i(this,"marginRight")+w.width,S=c+b+i(this,"marginBottom")+w.height,M=e.extend({},y),C=t(T.my,u.outerWidth(),u.outerHeight());"right"===n.my[0]?M.left-=d:"center"===n.my[0]&&(M.left-=d/2),"bottom"===n.my[1]?M.top-=c:"center"===n.my[1]&&(M.top-=c/2),M.left+=C[0],M.top+=C[1],a||(M.left=h(M.left),M.top=h(M.top)),s={marginLeft:f,marginTop:b},e.each(["left","top"],function(t,i){e.ui.position[k[t]]&&e.ui.position[k[t]][i](M,{targetWidth:m,targetHeight:g,elemWidth:d,elemHeight:c,collisionPosition:s,collisionWidth:D,collisionHeight:S,offset:[p[0]+C[0],p[1]+C[1]],my:n.my,at:n.at,within:x,elem:u})}),n.using&&(l=function(e){var t=v.left-M.left,i=t+m-d,s=v.top-M.top,a=s+g-c,h={target:{element:_,left:v.left,top:v.top,width:m,height:g},element:{element:u,left:M.left,top:M.top,width:d,height:c},horizontal:0>i?"left":t>0?"right":"center",vertical:0>a?"top":s>0?"bottom":"middle"};d>m&&m>r(t+i)&&(h.horizontal="center"),c>g&&g>r(s+a)&&(h.vertical="middle"),h.important=o(r(t),r(i))>o(r(s),r(a))?"horizontal":"vertical",n.using.call(this,e,h)}),u.offset(e.extend(M,{using:l}))})},e.ui.position={fit:{left:function(e,t){var i,s=t.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=e.left-t.collisionPosition.marginLeft,h=n-r,l=r+t.collisionWidth-a-n;t.collisionWidth>a?h>0&&0>=l?(i=e.left+h+t.collisionWidth-a-n,e.left+=h-i):e.left=l>0&&0>=h?n:h>l?n+a-t.collisionWidth:n:h>0?e.left+=h:l>0?e.left-=l:e.left=o(e.left-r,e.left)},top:function(e,t){var i,s=t.within,n=s.isWindow?s.scrollTop:s.offset.top,a=t.within.height,r=e.top-t.collisionPosition.marginTop,h=n-r,l=r+t.collisionHeight-a-n;t.collisionHeight>a?h>0&&0>=l?(i=e.top+h+t.collisionHeight-a-n,e.top+=h-i):e.top=l>0&&0>=h?n:h>l?n+a-t.collisionHeight:n:h>0?e.top+=h:l>0?e.top-=l:e.top=o(e.top-r,e.top)}},flip:{left:function(e,t){var i,s,n=t.within,a=n.offset.left+n.scrollLeft,o=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=e.left-t.collisionPosition.marginLeft,u=l-h,d=l+t.collisionWidth-o-h,c="left"===t.my[0]?-t.elemWidth:"right"===t.my[0]?t.elemWidth:0,p="left"===t.at[0]?t.targetWidth:"right"===t.at[0]?-t.targetWidth:0,f=-2*t.offset[0];0>u?(i=e.left+c+p+f+t.collisionWidth-o-a,(0>i||r(u)>i)&&(e.left+=c+p+f)):d>0&&(s=e.left-t.collisionPosition.marginLeft+c+p+f-h,(s>0||d>r(s))&&(e.left+=c+p+f))},top:function(e,t){var i,s,n=t.within,a=n.offset.top+n.scrollTop,o=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=e.top-t.collisionPosition.marginTop,u=l-h,d=l+t.collisionHeight-o-h,c="top"===t.my[1],p=c?-t.elemHeight:"bottom"===t.my[1]?t.elemHeight:0,f="top"===t.at[1]?t.targetHeight:"bottom"===t.at[1]?-t.targetHeight:0,m=-2*t.offset[1];0>u?(s=e.top+p+f+m+t.collisionHeight-o-a,(0>s||r(u)>s)&&(e.top+=p+f+m)):d>0&&(i=e.top-t.collisionPosition.marginTop+p+f+m-h,(i>0||d>r(i))&&(e.top+=p+f+m))}},flipfit:{left:function(){e.ui.position.flip.left.apply(this,arguments),e.ui.position.fit.left.apply(this,arguments)},top:function(){e.ui.position.flip.top.apply(this,arguments),e.ui.position.fit.top.apply(this,arguments)}}},function(){var t,i,s,n,o,r=document.getElementsByTagName("body")[0],h=document.createElement("div");t=document.createElement(r?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},r&&e.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(o in s)t.style[o]=s[o];t.appendChild(h),i=r||document.documentElement,i.insertBefore(t,i.firstChild),h.style.cssText="position: absolute; left: 10.7432222px;",n=e(h).offset().left,a=n>10&&11>n,t.innerHTML="",i.removeChild(t)}()}(),e.ui.position,e.widget("ui.draggable",e.ui.mouse,{version:"1.11.4",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"===this.options.helper&&this._setPositionRelative(),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._setHandleClassName(),this._mouseInit()},_setOption:function(e,t){this._super(e,t),"handle"===e&&(this._removeHandleClassName(),this._setHandleClassName())},_destroy:function(){return(this.helper||this.element).is(".ui-draggable-dragging")?(this.destroyOnClear=!0,void 0):(this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._removeHandleClassName(),this._mouseDestroy(),void 0)},_mouseCapture:function(t){var i=this.options;return this._blurActiveElement(t),this.helper||i.disabled||e(t.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(t),this.handle?(this._blockFrames(i.iframeFix===!0?"iframe":i.iframeFix),!0):!1)},_blockFrames:function(t){this.iframeBlocks=this.document.find(t).map(function(){var t=e(this);return e("<div>").css("position","absolute").appendTo(t.parent()).outerWidth(t.outerWidth()).outerHeight(t.outerHeight()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(t){var i=this.document[0];if(this.handleElement.is(t.target))try{i.activeElement&&"body"!==i.activeElement.nodeName.toLowerCase()&&e(i.activeElement).blur()}catch(s){}},_mouseStart:function(t){var i=this.options;return this.helper=this._createHelper(t),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),e.ui.ddmanager&&(e.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=this.helper.parents().filter(function(){return"fixed"===e(this).css("position")}).length>0,this.positionAbs=this.element.offset(),this._refreshOffsets(t),this.originalPosition=this.position=this._generatePosition(t,!1),this.originalPageX=t.pageX,this.originalPageY=t.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",t)===!1?(this._clear(),!1):(this._cacheHelperProportions(),e.ui.ddmanager&&!i.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this._normalizeRightBottom(),this._mouseDrag(t,!0),e.ui.ddmanager&&e.ui.ddmanager.dragStart(this,t),!0)},_refreshOffsets:function(e){this.offset={top:this.positionAbs.top-this.margins.top,left:this.positionAbs.left-this.margins.left,scroll:!1,parent:this._getParentOffset(),relative:this._getRelativeOffset()},this.offset.click={left:e.pageX-this.offset.left,top:e.pageY-this.offset.top}},_mouseDrag:function(t,i){if(this.hasFixedAncestor&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(t,!0),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",t,s)===!1)return this._mouseUp({}),!1;this.position=s.position}return this.helper[0].style.left=this.position.left+"px",this.helper[0].style.top=this.position.top+"px",e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),!1},_mouseStop:function(t){var i=this,s=!1;return e.ui.ddmanager&&!this.options.dropBehaviour&&(s=e.ui.ddmanager.drop(this,t)),this.dropped&&(s=this.dropped,this.dropped=!1),"invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||e.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?e(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",t)!==!1&&i._clear()}):this._trigger("stop",t)!==!1&&this._clear(),!1},_mouseUp:function(t){return this._unblockFrames(),e.ui.ddmanager&&e.ui.ddmanager.dragStop(this,t),this.handleElement.is(t.target)&&this.element.focus(),e.ui.mouse.prototype._mouseUp.call(this,t)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(t){return this.options.handle?!!e(t.target).closest(this.element.find(this.options.handle)).length:!0},_setHandleClassName:function(){this.handleElement=this.options.handle?this.element.find(this.options.handle):this.element,this.handleElement.addClass("ui-draggable-handle")},_removeHandleClassName:function(){this.handleElement.removeClass("ui-draggable-handle")},_createHelper:function(t){var i=this.options,s=e.isFunction(i.helper),n=s?e(i.helper.apply(this.element[0],[t])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return n.parents("body").length||n.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s&&n[0]===this.element[0]&&this._setPositionRelative(),n[0]===this.element[0]||/(fixed|absolute)/.test(n.css("position"))||n.css("position","absolute"),n},_setPositionRelative:function(){/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative")},_adjustOffsetFromHelper:function(t){"string"==typeof t&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_isRootNode:function(e){return/(html|body)/i.test(e.tagName)||e===this.document[0]},_getParentOffset:function(){var t=this.offsetParent.offset(),i=this.document[0];return"absolute"===this.cssPosition&&this.scrollParent[0]!==i&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop()),this._isRootNode(this.offsetParent[0])&&(t={top:0,left:0}),{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"!==this.cssPosition)return{top:0,left:0};var e=this.element.position(),t=this._isRootNode(this.scrollParent[0]);return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+(t?0:this.scrollParent.scrollTop()),left:e.left-(parseInt(this.helper.css("left"),10)||0)+(t?0:this.scrollParent.scrollLeft())}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,i,s,n=this.options,a=this.document[0];return this.relativeContainer=null,n.containment?"window"===n.containment?(this.containment=[e(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,e(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,e(window).scrollLeft()+e(window).width()-this.helperProportions.width-this.margins.left,e(window).scrollTop()+(e(window).height()||a.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):"document"===n.containment?(this.containment=[0,0,e(a).width()-this.helperProportions.width-this.margins.left,(e(a).height()||a.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):n.containment.constructor===Array?(this.containment=n.containment,void 0):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=e(n.containment),s=i[0],s&&(t=/(scroll|auto)/.test(i.css("overflow")),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(t?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(t?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relativeContainer=i),void 0):(this.containment=null,void 0)},_convertPositionTo:function(e,t){t||(t=this.position);var i="absolute"===e?1:-1,s=this._isRootNode(this.scrollParent[0]);return{top:t.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.offset.scroll.top:s?0:this.offset.scroll.top)*i,left:t.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.offset.scroll.left:s?0:this.offset.scroll.left)*i}},_generatePosition:function(e,t){var i,s,n,a,o=this.options,r=this._isRootNode(this.scrollParent[0]),h=e.pageX,l=e.pageY;return r&&this.offset.scroll||(this.offset.scroll={top:this.scrollParent.scrollTop(),left:this.scrollParent.scrollLeft()}),t&&(this.containment&&(this.relativeContainer?(s=this.relativeContainer.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,e.pageX-this.offset.click.left<i[0]&&(h=i[0]+this.offset.click.left),e.pageY-this.offset.click.top<i[1]&&(l=i[1]+this.offset.click.top),e.pageX-this.offset.click.left>i[2]&&(h=i[2]+this.offset.click.left),e.pageY-this.offset.click.top>i[3]&&(l=i[3]+this.offset.click.top)),o.grid&&(n=o.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/o.grid[1])*o.grid[1]:this.originalPageY,l=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-o.grid[1]:n+o.grid[1]:n,a=o.grid[0]?this.originalPageX+Math.round((h-this.originalPageX)/o.grid[0])*o.grid[0]:this.originalPageX,h=i?a-this.offset.click.left>=i[0]||a-this.offset.click.left>i[2]?a:a-this.offset.click.left>=i[0]?a-o.grid[0]:a+o.grid[0]:a),"y"===o.axis&&(h=this.originalPageX),"x"===o.axis&&(l=this.originalPageY)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:r?0:this.offset.scroll.top),left:h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:r?0:this.offset.scroll.left)}
+},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_normalizeRightBottom:function(){"y"!==this.options.axis&&"auto"!==this.helper.css("right")&&(this.helper.width(this.helper.width()),this.helper.css("right","auto")),"x"!==this.options.axis&&"auto"!==this.helper.css("bottom")&&(this.helper.height(this.helper.height()),this.helper.css("bottom","auto"))},_trigger:function(t,i,s){return s=s||this._uiHash(),e.ui.plugin.call(this,t,[i,s,this],!0),/^(drag|start|stop)/.test(t)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),e.Widget.prototype._trigger.call(this,t,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),e.ui.plugin.add("draggable","connectToSortable",{start:function(t,i,s){var n=e.extend({},i,{item:s.element});s.sortables=[],e(s.options.connectToSortable).each(function(){var i=e(this).sortable("instance");i&&!i.options.disabled&&(s.sortables.push(i),i.refreshPositions(),i._trigger("activate",t,n))})},stop:function(t,i,s){var n=e.extend({},i,{item:s.element});s.cancelHelperRemoval=!1,e.each(s.sortables,function(){var e=this;e.isOver?(e.isOver=0,s.cancelHelperRemoval=!0,e.cancelHelperRemoval=!1,e._storedCSS={position:e.placeholder.css("position"),top:e.placeholder.css("top"),left:e.placeholder.css("left")},e._mouseStop(t),e.options.helper=e.options._helper):(e.cancelHelperRemoval=!0,e._trigger("deactivate",t,n))})},drag:function(t,i,s){e.each(s.sortables,function(){var n=!1,a=this;a.positionAbs=s.positionAbs,a.helperProportions=s.helperProportions,a.offset.click=s.offset.click,a._intersectsWith(a.containerCache)&&(n=!0,e.each(s.sortables,function(){return this.positionAbs=s.positionAbs,this.helperProportions=s.helperProportions,this.offset.click=s.offset.click,this!==a&&this._intersectsWith(this.containerCache)&&e.contains(a.element[0],this.element[0])&&(n=!1),n})),n?(a.isOver||(a.isOver=1,s._parent=i.helper.parent(),a.currentItem=i.helper.appendTo(a.element).data("ui-sortable-item",!0),a.options._helper=a.options.helper,a.options.helper=function(){return i.helper[0]},t.target=a.currentItem[0],a._mouseCapture(t,!0),a._mouseStart(t,!0,!0),a.offset.click.top=s.offset.click.top,a.offset.click.left=s.offset.click.left,a.offset.parent.left-=s.offset.parent.left-a.offset.parent.left,a.offset.parent.top-=s.offset.parent.top-a.offset.parent.top,s._trigger("toSortable",t),s.dropped=a.element,e.each(s.sortables,function(){this.refreshPositions()}),s.currentItem=s.element,a.fromOutside=s),a.currentItem&&(a._mouseDrag(t),i.position=a.position)):a.isOver&&(a.isOver=0,a.cancelHelperRemoval=!0,a.options._revert=a.options.revert,a.options.revert=!1,a._trigger("out",t,a._uiHash(a)),a._mouseStop(t,!0),a.options.revert=a.options._revert,a.options.helper=a.options._helper,a.placeholder&&a.placeholder.remove(),i.helper.appendTo(s._parent),s._refreshOffsets(t),i.position=s._generatePosition(t,!0),s._trigger("fromSortable",t),s.dropped=!1,e.each(s.sortables,function(){this.refreshPositions()}))})}}),e.ui.plugin.add("draggable","cursor",{start:function(t,i,s){var n=e("body"),a=s.options;n.css("cursor")&&(a._cursor=n.css("cursor")),n.css("cursor",a.cursor)},stop:function(t,i,s){var n=s.options;n._cursor&&e("body").css("cursor",n._cursor)}}),e.ui.plugin.add("draggable","opacity",{start:function(t,i,s){var n=e(i.helper),a=s.options;n.css("opacity")&&(a._opacity=n.css("opacity")),n.css("opacity",a.opacity)},stop:function(t,i,s){var n=s.options;n._opacity&&e(i.helper).css("opacity",n._opacity)}}),e.ui.plugin.add("draggable","scroll",{start:function(e,t,i){i.scrollParentNotHidden||(i.scrollParentNotHidden=i.helper.scrollParent(!1)),i.scrollParentNotHidden[0]!==i.document[0]&&"HTML"!==i.scrollParentNotHidden[0].tagName&&(i.overflowOffset=i.scrollParentNotHidden.offset())},drag:function(t,i,s){var n=s.options,a=!1,o=s.scrollParentNotHidden[0],r=s.document[0];o!==r&&"HTML"!==o.tagName?(n.axis&&"x"===n.axis||(s.overflowOffset.top+o.offsetHeight-t.pageY<n.scrollSensitivity?o.scrollTop=a=o.scrollTop+n.scrollSpeed:t.pageY-s.overflowOffset.top<n.scrollSensitivity&&(o.scrollTop=a=o.scrollTop-n.scrollSpeed)),n.axis&&"y"===n.axis||(s.overflowOffset.left+o.offsetWidth-t.pageX<n.scrollSensitivity?o.scrollLeft=a=o.scrollLeft+n.scrollSpeed:t.pageX-s.overflowOffset.left<n.scrollSensitivity&&(o.scrollLeft=a=o.scrollLeft-n.scrollSpeed))):(n.axis&&"x"===n.axis||(t.pageY-e(r).scrollTop()<n.scrollSensitivity?a=e(r).scrollTop(e(r).scrollTop()-n.scrollSpeed):e(window).height()-(t.pageY-e(r).scrollTop())<n.scrollSensitivity&&(a=e(r).scrollTop(e(r).scrollTop()+n.scrollSpeed))),n.axis&&"y"===n.axis||(t.pageX-e(r).scrollLeft()<n.scrollSensitivity?a=e(r).scrollLeft(e(r).scrollLeft()-n.scrollSpeed):e(window).width()-(t.pageX-e(r).scrollLeft())<n.scrollSensitivity&&(a=e(r).scrollLeft(e(r).scrollLeft()+n.scrollSpeed)))),a!==!1&&e.ui.ddmanager&&!n.dropBehaviour&&e.ui.ddmanager.prepareOffsets(s,t)}}),e.ui.plugin.add("draggable","snap",{start:function(t,i,s){var n=s.options;s.snapElements=[],e(n.snap.constructor!==String?n.snap.items||":data(ui-draggable)":n.snap).each(function(){var t=e(this),i=t.offset();this!==s.element[0]&&s.snapElements.push({item:this,width:t.outerWidth(),height:t.outerHeight(),top:i.top,left:i.left})})},drag:function(t,i,s){var n,a,o,r,h,l,u,d,c,p,f=s.options,m=f.snapTolerance,g=i.offset.left,v=g+s.helperProportions.width,y=i.offset.top,b=y+s.helperProportions.height;for(c=s.snapElements.length-1;c>=0;c--)h=s.snapElements[c].left-s.margins.left,l=h+s.snapElements[c].width,u=s.snapElements[c].top-s.margins.top,d=u+s.snapElements[c].height,h-m>v||g>l+m||u-m>b||y>d+m||!e.contains(s.snapElements[c].item.ownerDocument,s.snapElements[c].item)?(s.snapElements[c].snapping&&s.options.snap.release&&s.options.snap.release.call(s.element,t,e.extend(s._uiHash(),{snapItem:s.snapElements[c].item})),s.snapElements[c].snapping=!1):("inner"!==f.snapMode&&(n=m>=Math.abs(u-b),a=m>=Math.abs(d-y),o=m>=Math.abs(h-v),r=m>=Math.abs(l-g),n&&(i.position.top=s._convertPositionTo("relative",{top:u-s.helperProportions.height,left:0}).top),a&&(i.position.top=s._convertPositionTo("relative",{top:d,left:0}).top),o&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h-s.helperProportions.width}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l}).left)),p=n||a||o||r,"outer"!==f.snapMode&&(n=m>=Math.abs(u-y),a=m>=Math.abs(d-b),o=m>=Math.abs(h-g),r=m>=Math.abs(l-v),n&&(i.position.top=s._convertPositionTo("relative",{top:u,left:0}).top),a&&(i.position.top=s._convertPositionTo("relative",{top:d-s.helperProportions.height,left:0}).top),o&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l-s.helperProportions.width}).left)),!s.snapElements[c].snapping&&(n||a||o||r||p)&&s.options.snap.snap&&s.options.snap.snap.call(s.element,t,e.extend(s._uiHash(),{snapItem:s.snapElements[c].item})),s.snapElements[c].snapping=n||a||o||r||p)}}),e.ui.plugin.add("draggable","stack",{start:function(t,i,s){var n,a=s.options,o=e.makeArray(e(a.stack)).sort(function(t,i){return(parseInt(e(t).css("zIndex"),10)||0)-(parseInt(e(i).css("zIndex"),10)||0)});o.length&&(n=parseInt(e(o[0]).css("zIndex"),10)||0,e(o).each(function(t){e(this).css("zIndex",n+t)}),this.css("zIndex",n+o.length))}}),e.ui.plugin.add("draggable","zIndex",{start:function(t,i,s){var n=e(i.helper),a=s.options;n.css("zIndex")&&(a._zIndex=n.css("zIndex")),n.css("zIndex",a.zIndex)},stop:function(t,i,s){var n=s.options;n._zIndex&&e(i.helper).css("zIndex",n._zIndex)}}),e.ui.draggable,e.widget("ui.droppable",{version:"1.11.4",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var t,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=e.isFunction(s)?s:function(e){return e.is(s)},this.proportions=function(){return arguments.length?(t=arguments[0],void 0):t?t:t={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}},this._addToManager(i.scope),i.addClasses&&this.element.addClass("ui-droppable")},_addToManager:function(t){e.ui.ddmanager.droppables[t]=e.ui.ddmanager.droppables[t]||[],e.ui.ddmanager.droppables[t].push(this)},_splice:function(e){for(var t=0;e.length>t;t++)e[t]===this&&e.splice(t,1)},_destroy:function(){var t=e.ui.ddmanager.droppables[this.options.scope];this._splice(t),this.element.removeClass("ui-droppable ui-droppable-disabled")},_setOption:function(t,i){if("accept"===t)this.accept=e.isFunction(i)?i:function(e){return e.is(i)};else if("scope"===t){var s=e.ui.ddmanager.droppables[this.options.scope];this._splice(s),this._addToManager(i)}this._super(t,i)},_activate:function(t){var i=e.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),i&&this._trigger("activate",t,this.ui(i))},_deactivate:function(t){var i=e.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),i&&this._trigger("deactivate",t,this.ui(i))},_over:function(t){var i=e.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",t,this.ui(i)))},_out:function(t){var i=e.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",t,this.ui(i)))},_drop:function(t,i){var s=i||e.ui.ddmanager.current,n=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var i=e(this).droppable("instance");return i.options.greedy&&!i.options.disabled&&i.options.scope===s.options.scope&&i.accept.call(i.element[0],s.currentItem||s.element)&&e.ui.intersect(s,e.extend(i,{offset:i.element.offset()}),i.options.tolerance,t)?(n=!0,!1):void 0}),n?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",t,this.ui(s)),this.element):!1):!1},ui:function(e){return{draggable:e.currentItem||e.element,helper:e.helper,position:e.position,offset:e.positionAbs}}}),e.ui.intersect=function(){function e(e,t,i){return e>=t&&t+i>e}return function(t,i,s,n){if(!i.offset)return!1;var a=(t.positionAbs||t.position.absolute).left+t.margins.left,o=(t.positionAbs||t.position.absolute).top+t.margins.top,r=a+t.helperProportions.width,h=o+t.helperProportions.height,l=i.offset.left,u=i.offset.top,d=l+i.proportions().width,c=u+i.proportions().height;switch(s){case"fit":return a>=l&&d>=r&&o>=u&&c>=h;case"intersect":return a+t.helperProportions.width/2>l&&d>r-t.helperProportions.width/2&&o+t.helperProportions.height/2>u&&c>h-t.helperProportions.height/2;case"pointer":return e(n.pageY,u,i.proportions().height)&&e(n.pageX,l,i.proportions().width);case"touch":return(o>=u&&c>=o||h>=u&&c>=h||u>o&&h>c)&&(a>=l&&d>=a||r>=l&&d>=r||l>a&&r>d);default:return!1}}}(),e.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(t,i){var s,n,a=e.ui.ddmanager.droppables[t.options.scope]||[],o=i?i.type:null,r=(t.currentItem||t.element).find(":data(ui-droppable)").addBack();e:for(s=0;a.length>s;s++)if(!(a[s].options.disabled||t&&!a[s].accept.call(a[s].element[0],t.currentItem||t.element))){for(n=0;r.length>n;n++)if(r[n]===a[s].element[0]){a[s].proportions().height=0;continue e}a[s].visible="none"!==a[s].element.css("display"),a[s].visible&&("mousedown"===o&&a[s]._activate.call(a[s],i),a[s].offset=a[s].element.offset(),a[s].proportions({width:a[s].element[0].offsetWidth,height:a[s].element[0].offsetHeight}))}},drop:function(t,i){var s=!1;return e.each((e.ui.ddmanager.droppables[t.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&e.ui.intersect(t,this,this.options.tolerance,i)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],t.currentItem||t.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(t,i){t.element.parentsUntil("body").bind("scroll.droppable",function(){t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,i)})},drag:function(t,i){t.options.refreshPositions&&e.ui.ddmanager.prepareOffsets(t,i),e.each(e.ui.ddmanager.droppables[t.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,a,o=e.ui.intersect(t,this,this.options.tolerance,i),r=!o&&this.isover?"isout":o&&!this.isover?"isover":null;r&&(this.options.greedy&&(n=this.options.scope,a=this.element.parents(":data(ui-droppable)").filter(function(){return e(this).droppable("instance").options.scope===n}),a.length&&(s=e(a[0]).droppable("instance"),s.greedyChild="isover"===r)),s&&"isover"===r&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[r]=!0,this["isout"===r?"isover":"isout"]=!1,this["isover"===r?"_over":"_out"].call(this,i),s&&"isout"===r&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(t,i){t.element.parentsUntil("body").unbind("scroll.droppable"),t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,i)}},e.ui.droppable,e.widget("ui.resizable",e.ui.mouse,{version:"1.11.4",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(e){return parseInt(e,10)||0},_isNumber:function(e){return!isNaN(parseInt(e,10))},_hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",n=!1;return t[s]>0?!0:(t[s]=1,n=t[s]>0,t[s]=0,n)},_create:function(){var t,i,s,n,a,o=this,r=this.options;if(this.element.addClass("ui-resizable"),e.extend(this,{_aspectRatio:!!r.aspectRatio,aspectRatio:r.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:r.helper||r.ghost||r.animate?r.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(e("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=r.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=e(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),t=this.handles.split(","),this.handles={},i=0;t.length>i;i++)s=e.trim(t[i]),a="ui-resizable-"+s,n=e("<div class='ui-resizable-handle "+a+"'></div>"),n.css({zIndex:r.zIndex}),"se"===s&&n.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(n);this._renderAxis=function(t){var i,s,n,a;t=t||this.element;for(i in this.handles)this.handles[i].constructor===String?this.handles[i]=this.element.children(this.handles[i]).first().show():(this.handles[i].jquery||this.handles[i].nodeType)&&(this.handles[i]=e(this.handles[i]),this._on(this.handles[i],{mousedown:o._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(s=e(this.handles[i],this.element),a=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),t.css(n,a),this._proportionallyResize()),this._handles=this._handles.add(this.handles[i])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.mouseover(function(){o.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),o.axis=n&&n[1]?n[1]:"se")}),r.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){r.disabled||(e(this).removeClass("ui-resizable-autohide"),o._handles.show())}).mouseleave(function(){r.disabled||o.resizing||(e(this).addClass("ui-resizable-autohide"),o._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t,i=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),t=this.element,this.originalElement.css({position:t.css("position"),width:t.outerWidth(),height:t.outerHeight(),top:t.css("top"),left:t.css("left")}).insertAfter(t),t.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_mouseCapture:function(t){var i,s,n=!1;for(i in this.handles)s=e(this.handles[i])[0],(s===t.target||e.contains(s,t.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(t){var i,s,n,a=this.options,o=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),a.containment&&(i+=e(a.containment).scrollLeft()||0,s+=e(a.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:o.width(),height:o.height()},this.originalSize=this._helper?{width:o.outerWidth(),height:o.outerHeight()}:{width:o.width(),height:o.height()},this.sizeDiff={width:o.outerWidth()-o.width(),height:o.outerHeight()-o.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio="number"==typeof a.aspectRatio?a.aspectRatio:this.originalSize.width/this.originalSize.height||1,n=e(".ui-resizable-"+this.axis).css("cursor"),e("body").css("cursor","auto"===n?this.axis+"-resize":n),o.addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(t){var i,s,n=this.originalMousePosition,a=this.axis,o=t.pageX-n.left||0,r=t.pageY-n.top||0,h=this._change[a];return this._updatePrevProperties(),h?(i=h.apply(this,[t,o,r]),this._updateVirtualBoundaries(t.shiftKey),(this._aspectRatio||t.shiftKey)&&(i=this._updateRatio(i,t)),i=this._respectSize(i,t),this._updateCache(i),this._propagate("resize",t),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),e.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",t,this.ui()),this._applyChanges()),!1):!1},_mouseStop:function(t){this.resizing=!1;var i,s,n,a,o,r,h,l=this.options,u=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&this._hasScroll(i[0],"left")?0:u.sizeDiff.height,a=s?0:u.sizeDiff.width,o={width:u.helper.width()-a,height:u.helper.height()-n},r=parseInt(u.element.css("left"),10)+(u.position.left-u.originalPosition.left)||null,h=parseInt(u.element.css("top"),10)+(u.position.top-u.originalPosition.top)||null,l.animate||this.element.css(e.extend(o,{top:h,left:r})),u.helper.height(u.size.height),u.helper.width(u.size.width),this._helper&&!l.animate&&this._proportionallyResize()),e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var e={};return this.position.top!==this.prevPosition.top&&(e.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(e.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(e.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(e.height=this.size.height+"px"),this.helper.css(e),e},_updateVirtualBoundaries:function(e){var t,i,s,n,a,o=this.options;a={minWidth:this._isNumber(o.minWidth)?o.minWidth:0,maxWidth:this._isNumber(o.maxWidth)?o.maxWidth:1/0,minHeight:this._isNumber(o.minHeight)?o.minHeight:0,maxHeight:this._isNumber(o.maxHeight)?o.maxHeight:1/0},(this._aspectRatio||e)&&(t=a.minHeight*this.aspectRatio,s=a.minWidth/this.aspectRatio,i=a.maxHeight*this.aspectRatio,n=a.maxWidth/this.aspectRatio,t>a.minWidth&&(a.minWidth=t),s>a.minHeight&&(a.minHeight=s),a.maxWidth>i&&(a.maxWidth=i),a.maxHeight>n&&(a.maxHeight=n)),this._vBoundaries=a},_updateCache:function(e){this.offset=this.helper.offset(),this._isNumber(e.left)&&(this.position.left=e.left),this._isNumber(e.top)&&(this.position.top=e.top),this._isNumber(e.height)&&(this.size.height=e.height),this._isNumber(e.width)&&(this.size.width=e.width)},_updateRatio:function(e){var t=this.position,i=this.size,s=this.axis;return this._isNumber(e.height)?e.width=e.height*this.aspectRatio:this._isNumber(e.width)&&(e.height=e.width/this.aspectRatio),"sw"===s&&(e.left=t.left+(i.width-e.width),e.top=null),"nw"===s&&(e.top=t.top+(i.height-e.height),e.left=t.left+(i.width-e.width)),e},_respectSize:function(e){var t=this._vBoundaries,i=this.axis,s=this._isNumber(e.width)&&t.maxWidth&&t.maxWidth<e.width,n=this._isNumber(e.height)&&t.maxHeight&&t.maxHeight<e.height,a=this._isNumber(e.width)&&t.minWidth&&t.minWidth>e.width,o=this._isNumber(e.height)&&t.minHeight&&t.minHeight>e.height,r=this.originalPosition.left+this.originalSize.width,h=this.position.top+this.size.height,l=/sw|nw|w/.test(i),u=/nw|ne|n/.test(i);return a&&(e.width=t.minWidth),o&&(e.height=t.minHeight),s&&(e.width=t.maxWidth),n&&(e.height=t.maxHeight),a&&l&&(e.left=r-t.minWidth),s&&l&&(e.left=r-t.maxWidth),o&&u&&(e.top=h-t.minHeight),n&&u&&(e.top=h-t.maxHeight),e.width||e.height||e.left||!e.top?e.width||e.height||e.top||!e.left||(e.left=null):e.top=null,e},_getPaddingPlusBorderDimensions:function(e){for(var t=0,i=[],s=[e.css("borderTopWidth"),e.css("borderRightWidth"),e.css("borderBottomWidth"),e.css("borderLeftWidth")],n=[e.css("paddingTop"),e.css("paddingRight"),e.css("paddingBottom"),e.css("paddingLeft")];4>t;t++)i[t]=parseInt(s[t],10)||0,i[t]+=parseInt(n[t],10)||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var e,t=0,i=this.helper||this.element;this._proportionallyResizeElements.length>t;t++)e=this._proportionallyResizeElements[t],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(e)),e.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var t=this.element,i=this.options;this.elementOffset=t.offset(),this._helper?(this.helper=this.helper||e("<div style='overflow:hidden;'></div>"),this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(e,t){return{width:this.originalSize.width+t}},w:function(e,t){var i=this.originalSize,s=this.originalPosition;return{left:s.left+t,width:i.width-t}},n:function(e,t,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(e,t,i){return{height:this.originalSize.height+i}},se:function(t,i,s){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,i,s]))},sw:function(t,i,s){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,i,s]))},ne:function(t,i,s){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,i,s]))},nw:function(t,i,s){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,i,s]))}},_propagate:function(t,i){e.ui.plugin.call(this,t,[i,this.ui()]),"resize"!==t&&this._trigger(t,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","animate",{stop:function(t){var i=e(this).resizable("instance"),s=i.options,n=i._proportionallyResizeElements,a=n.length&&/textarea/i.test(n[0].nodeName),o=a&&i._hasScroll(n[0],"left")?0:i.sizeDiff.height,r=a?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-o},l=parseInt(i.element.css("left"),10)+(i.position.left-i.originalPosition.left)||null,u=parseInt(i.element.css("top"),10)+(i.position.top-i.originalPosition.top)||null;i.element.animate(e.extend(h,u&&l?{top:u,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseInt(i.element.css("width"),10),height:parseInt(i.element.css("height"),10),top:parseInt(i.element.css("top"),10),left:parseInt(i.element.css("left"),10)};n&&n.length&&e(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(){var t,i,s,n,a,o,r,h=e(this).resizable("instance"),l=h.options,u=h.element,d=l.containment,c=d instanceof e?d.get(0):/parent/.test(d)?u.parent().get(0):d;c&&(h.containerElement=e(c),/document/.test(d)||d===document?(h.containerOffset={left:0,top:0},h.containerPosition={left:0,top:0},h.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}):(t=e(c),i=[],e(["Top","Right","Left","Bottom"]).each(function(e,s){i[e]=h._num(t.css("padding"+s))}),h.containerOffset=t.offset(),h.containerPosition=t.position(),h.containerSize={height:t.innerHeight()-i[3],width:t.innerWidth()-i[1]},s=h.containerOffset,n=h.containerSize.height,a=h.containerSize.width,o=h._hasScroll(c,"left")?c.scrollWidth:a,r=h._hasScroll(c)?c.scrollHeight:n,h.parentData={element:c,left:s.left,top:s.top,width:o,height:r}))},resize:function(t){var i,s,n,a,o=e(this).resizable("instance"),r=o.options,h=o.containerOffset,l=o.position,u=o._aspectRatio||t.shiftKey,d={top:0,left:0},c=o.containerElement,p=!0;c[0]!==document&&/static/.test(c.css("position"))&&(d=h),l.left<(o._helper?h.left:0)&&(o.size.width=o.size.width+(o._helper?o.position.left-h.left:o.position.left-d.left),u&&(o.size.height=o.size.width/o.aspectRatio,p=!1),o.position.left=r.helper?h.left:0),l.top<(o._helper?h.top:0)&&(o.size.height=o.size.height+(o._helper?o.position.top-h.top:o.position.top),u&&(o.size.width=o.size.height*o.aspectRatio,p=!1),o.position.top=o._helper?h.top:0),n=o.containerElement.get(0)===o.element.parent().get(0),a=/relative|absolute/.test(o.containerElement.css("position")),n&&a?(o.offset.left=o.parentData.left+o.position.left,o.offset.top=o.parentData.top+o.position.top):(o.offset.left=o.element.offset().left,o.offset.top=o.element.offset().top),i=Math.abs(o.sizeDiff.width+(o._helper?o.offset.left-d.left:o.offset.left-h.left)),s=Math.abs(o.sizeDiff.height+(o._helper?o.offset.top-d.top:o.offset.top-h.top)),i+o.size.width>=o.parentData.width&&(o.size.width=o.parentData.width-i,u&&(o.size.height=o.size.width/o.aspectRatio,p=!1)),s+o.size.height>=o.parentData.height&&(o.size.height=o.parentData.height-s,u&&(o.size.width=o.size.height*o.aspectRatio,p=!1)),p||(o.position.left=o.prevPosition.left,o.position.top=o.prevPosition.top,o.size.width=o.prevSize.width,o.size.height=o.prevSize.height)},stop:function(){var t=e(this).resizable("instance"),i=t.options,s=t.containerOffset,n=t.containerPosition,a=t.containerElement,o=e(t.helper),r=o.offset(),h=o.outerWidth()-t.sizeDiff.width,l=o.outerHeight()-t.sizeDiff.height;t._helper&&!i.animate&&/relative/.test(a.css("position"))&&e(this).css({left:r.left-n.left-s.left,width:h,height:l}),t._helper&&!i.animate&&/static/.test(a.css("position"))&&e(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),e.ui.plugin.add("resizable","alsoResize",{start:function(){var t=e(this).resizable("instance"),i=t.options;e(i.alsoResize).each(function(){var t=e(this);t.data("ui-resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})},resize:function(t,i){var s=e(this).resizable("instance"),n=s.options,a=s.originalSize,o=s.originalPosition,r={height:s.size.height-a.height||0,width:s.size.width-a.width||0,top:s.position.top-o.top||0,left:s.position.left-o.left||0};e(n.alsoResize).each(function(){var t=e(this),s=e(this).data("ui-resizable-alsoresize"),n={},a=t.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(a,function(e,t){var i=(s[t]||0)+(r[t]||0);i&&i>=0&&(n[t]=i||null)}),t.css(n)})},stop:function(){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","ghost",{start:function(){var t=e(this).resizable("instance"),i=t.options,s=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass("string"==typeof i.ghost?i.ghost:""),t.ghost.appendTo(t.helper)},resize:function(){var t=e(this).resizable("instance");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=e(this).resizable("instance");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(){var t,i=e(this).resizable("instance"),s=i.options,n=i.size,a=i.originalSize,o=i.originalPosition,r=i.axis,h="number"==typeof s.grid?[s.grid,s.grid]:s.grid,l=h[0]||1,u=h[1]||1,d=Math.round((n.width-a.width)/l)*l,c=Math.round((n.height-a.height)/u)*u,p=a.width+d,f=a.height+c,m=s.maxWidth&&p>s.maxWidth,g=s.maxHeight&&f>s.maxHeight,v=s.minWidth&&s.minWidth>p,y=s.minHeight&&s.minHeight>f;s.grid=h,v&&(p+=l),y&&(f+=u),m&&(p-=l),g&&(f-=u),/^(se|s|e)$/.test(r)?(i.size.width=p,i.size.height=f):/^(ne)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.top=o.top-c):/^(sw)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.left=o.left-d):((0>=f-u||0>=p-l)&&(t=i._getPaddingPlusBorderDimensions(this)),f-u>0?(i.size.height=f,i.position.top=o.top-c):(f=u-t.height,i.size.height=f,i.position.top=o.top+a.height-f),p-l>0?(i.size.width=p,i.position.left=o.left-d):(p=l-t.width,i.size.width=p,i.position.left=o.left+a.width-p))}}),e.ui.resizable,e.widget("ui.selectable",e.ui.mouse,{version:"1.11.4",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var t,i=this;
+this.element.addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){t=e(i.options.filter,i.element[0]),t.addClass("ui-selectee"),t.each(function(){var t=e(this),i=t.offset();e.data(this,"selectable-item",{element:this,$element:t,left:i.left,top:i.top,right:i.left+t.outerWidth(),bottom:i.top+t.outerHeight(),startselected:!1,selected:t.hasClass("ui-selected"),selecting:t.hasClass("ui-selecting"),unselecting:t.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=t.addClass("ui-selectee"),this._mouseInit(),this.helper=e("<div class='ui-selectable-helper'></div>")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled"),this._mouseDestroy()},_mouseStart:function(t){var i=this,s=this.options;this.opos=[t.pageX,t.pageY],this.options.disabled||(this.selectees=e(s.filter,this.element[0]),this._trigger("start",t),e(s.appendTo).append(this.helper),this.helper.css({left:t.pageX,top:t.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=e.data(this,"selectable-item");s.startselected=!0,t.metaKey||t.ctrlKey||(s.$element.removeClass("ui-selected"),s.selected=!1,s.$element.addClass("ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",t,{unselecting:s.element}))}),e(t.target).parents().addBack().each(function(){var s,n=e.data(this,"selectable-item");return n?(s=!t.metaKey&&!t.ctrlKey||!n.$element.hasClass("ui-selected"),n.$element.removeClass(s?"ui-unselecting":"ui-selected").addClass(s?"ui-selecting":"ui-unselecting"),n.unselecting=!s,n.selecting=s,n.selected=s,s?i._trigger("selecting",t,{selecting:n.element}):i._trigger("unselecting",t,{unselecting:n.element}),!1):void 0}))},_mouseDrag:function(t){if(this.dragged=!0,!this.options.disabled){var i,s=this,n=this.options,a=this.opos[0],o=this.opos[1],r=t.pageX,h=t.pageY;return a>r&&(i=r,r=a,a=i),o>h&&(i=h,h=o,o=i),this.helper.css({left:a,top:o,width:r-a,height:h-o}),this.selectees.each(function(){var i=e.data(this,"selectable-item"),l=!1;i&&i.element!==s.element[0]&&("touch"===n.tolerance?l=!(i.left>r||a>i.right||i.top>h||o>i.bottom):"fit"===n.tolerance&&(l=i.left>a&&r>i.right&&i.top>o&&h>i.bottom),l?(i.selected&&(i.$element.removeClass("ui-selected"),i.selected=!1),i.unselecting&&(i.$element.removeClass("ui-unselecting"),i.unselecting=!1),i.selecting||(i.$element.addClass("ui-selecting"),i.selecting=!0,s._trigger("selecting",t,{selecting:i.element}))):(i.selecting&&((t.metaKey||t.ctrlKey)&&i.startselected?(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.$element.addClass("ui-selected"),i.selected=!0):(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.startselected&&(i.$element.addClass("ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",t,{unselecting:i.element}))),i.selected&&(t.metaKey||t.ctrlKey||i.startselected||(i.$element.removeClass("ui-selected"),i.selected=!1,i.$element.addClass("ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",t,{unselecting:i.element})))))}),!1}},_mouseStop:function(t){var i=this;return this.dragged=!1,e(".ui-unselecting",this.element[0]).each(function(){var s=e.data(this,"selectable-item");s.$element.removeClass("ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",t,{unselected:s.element})}),e(".ui-selecting",this.element[0]).each(function(){var s=e.data(this,"selectable-item");s.$element.removeClass("ui-selecting").addClass("ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",t,{selected:s.element})}),this._trigger("stop",t),this.helper.remove(),!1}}),e.widget("ui.sortable",e.ui.mouse,{version:"1.11.4",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(e,t,i){return e>=t&&t+i>e},_isFloating:function(e){return/left|right/.test(e.css("float"))||/inline|table-cell/.test(e.css("display"))},_create:function(){this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.offset=this.element.offset(),this._mouseInit(),this._setHandleClassName(),this.ready=!0},_setOption:function(e,t){this._super(e,t),"handle"===e&&this._setHandleClassName()},_setHandleClassName:function(){this.element.find(".ui-sortable-handle").removeClass("ui-sortable-handle"),e.each(this.items,function(){(this.instance.options.handle?this.item.find(this.instance.options.handle):this.item).addClass("ui-sortable-handle")})},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").find(".ui-sortable-handle").removeClass("ui-sortable-handle"),this._mouseDestroy();for(var e=this.items.length-1;e>=0;e--)this.items[e].item.removeData(this.widgetName+"-item");return this},_mouseCapture:function(t,i){var s=null,n=!1,a=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(t),e(t.target).parents().each(function(){return e.data(this,a.widgetName+"-item")===a?(s=e(this),!1):void 0}),e.data(t.target,a.widgetName+"-item")===a&&(s=e(t.target)),s?!this.options.handle||i||(e(this.options.handle,s).find("*").addBack().each(function(){this===t.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(t,i,s){var n,a,o=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(t),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,o.cursorAt&&this._adjustOffsetFromHelper(o.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),o.containment&&this._setContainment(),o.cursor&&"auto"!==o.cursor&&(a=this.document.find("body"),this.storedCursor=a.css("cursor"),a.css("cursor",o.cursor),this.storedStylesheet=e("<style>*{ cursor: "+o.cursor+" !important; }</style>").appendTo(a)),o.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",o.opacity)),o.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",o.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",t,this._uiHash(this));return e.ui.ddmanager&&(e.ui.ddmanager.current=this),e.ui.ddmanager&&!o.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(t),!0},_mouseDrag:function(t){var i,s,n,a,o=this.options,r=!1;for(this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageY<o.scrollSensitivity?this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop+o.scrollSpeed:t.pageY-this.overflowOffset.top<o.scrollSensitivity&&(this.scrollParent[0].scrollTop=r=this.scrollParent[0].scrollTop-o.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-t.pageX<o.scrollSensitivity?this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft+o.scrollSpeed:t.pageX-this.overflowOffset.left<o.scrollSensitivity&&(this.scrollParent[0].scrollLeft=r=this.scrollParent[0].scrollLeft-o.scrollSpeed)):(t.pageY-this.document.scrollTop()<o.scrollSensitivity?r=this.document.scrollTop(this.document.scrollTop()-o.scrollSpeed):this.window.height()-(t.pageY-this.document.scrollTop())<o.scrollSensitivity&&(r=this.document.scrollTop(this.document.scrollTop()+o.scrollSpeed)),t.pageX-this.document.scrollLeft()<o.scrollSensitivity?r=this.document.scrollLeft(this.document.scrollLeft()-o.scrollSpeed):this.window.width()-(t.pageX-this.document.scrollLeft())<o.scrollSensitivity&&(r=this.document.scrollLeft(this.document.scrollLeft()+o.scrollSpeed))),r!==!1&&e.ui.ddmanager&&!o.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t)),this.positionAbs=this._convertPositionTo("absolute"),this.options.axis&&"y"===this.options.axis||(this.helper[0].style.left=this.position.left+"px"),this.options.axis&&"x"===this.options.axis||(this.helper[0].style.top=this.position.top+"px"),i=this.items.length-1;i>=0;i--)if(s=this.items[i],n=s.item[0],a=this._intersectsWithPointer(s),a&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===a?"next":"prev"]()[0]!==n&&!e.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!e.contains(this.element[0],n):!0)){if(this.direction=1===a?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(t,s),this._trigger("change",t,this._uiHash());break}return this._contactContainers(t),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),this._trigger("sort",t,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(t,i){if(t){if(e.ui.ddmanager&&!this.options.dropBehaviour&&e.ui.ddmanager.drop(this,t),this.options.revert){var s=this,n=this.placeholder.offset(),a=this.options.axis,o={};a&&"x"!==a||(o.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),a&&"y"!==a||(o.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,e(this.helper).animate(o,parseInt(this.options.revert,10)||500,function(){s._clear(t)})}else this._clear(t,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),"original"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var t=this.containers.length-1;t>=0;t--)this.containers[t]._trigger("deactivate",null,this._uiHash(this)),this.containers[t].containerCache.over&&(this.containers[t]._trigger("out",null,this._uiHash(this)),this.containers[t].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),e.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?e(this.domPosition.prev).after(this.currentItem):e(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(t){var i=this._getItemsAsjQuery(t&&t.connected),s=[];return t=t||{},e(i).each(function(){var i=(e(t.item||this).attr(t.attribute||"id")||"").match(t.expression||/(.+)[\-=_](.+)/);i&&s.push((t.key||i[1]+"[]")+"="+(t.key&&t.expression?i[1]:i[2]))}),!s.length&&t.key&&s.push(t.key+"="),s.join("&")},toArray:function(t){var i=this._getItemsAsjQuery(t&&t.connected),s=[];return t=t||{},i.each(function(){s.push(e(t.item||this).attr(t.attribute||"id")||"")}),s},_intersectsWith:function(e){var t=this.positionAbs.left,i=t+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,a=e.left,o=a+e.width,r=e.top,h=r+e.height,l=this.offset.click.top,u=this.offset.click.left,d="x"===this.options.axis||s+l>r&&h>s+l,c="y"===this.options.axis||t+u>a&&o>t+u,p=d&&c;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>e[this.floating?"width":"height"]?p:t+this.helperProportions.width/2>a&&o>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(e){var t="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,e.top,e.height),i="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,e.left,e.width),s=t&&i,n=this._getDragVerticalDirection(),a=this._getDragHorizontalDirection();return s?this.floating?a&&"right"===a||"down"===n?2:1:n&&("down"===n?2:1):!1},_intersectsWithSides:function(e){var t=this._isOverAxis(this.positionAbs.top+this.offset.click.top,e.top+e.height/2,e.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,e.left+e.width/2,e.width),s=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&i||"left"===n&&!i:s&&("down"===s&&t||"up"===s&&!t)},_getDragVerticalDirection:function(){var e=this.positionAbs.top-this.lastPositionAbs.top;return 0!==e&&(e>0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return 0!==e&&(e>0?"right":"left")},refresh:function(e){return this._refreshItems(e),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var e=this.options;return e.connectWith.constructor===String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(t){function i(){r.push(this)}var s,n,a,o,r=[],h=[],l=this._connectWith();if(l&&t)for(s=l.length-1;s>=0;s--)for(a=e(l[s],this.document[0]),n=a.length-1;n>=0;n--)o=e.data(a[n],this.widgetFullName),o&&o!==this&&!o.options.disabled&&h.push([e.isFunction(o.options.items)?o.options.items.call(o.element):e(o.options.items,o.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),o]);for(h.push([e.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):e(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return e(r)},_removeCurrentsFromItems:function(){var t=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=e.grep(this.items,function(e){for(var i=0;t.length>i;i++)if(t[i]===e.item[0])return!1;return!0})},_refreshItems:function(t){this.items=[],this.containers=[this];var i,s,n,a,o,r,h,l,u=this.items,d=[[e.isFunction(this.options.items)?this.options.items.call(this.element[0],t,{item:this.currentItem}):e(this.options.items,this.element),this]],c=this._connectWith();if(c&&this.ready)for(i=c.length-1;i>=0;i--)for(n=e(c[i],this.document[0]),s=n.length-1;s>=0;s--)a=e.data(n[s],this.widgetFullName),a&&a!==this&&!a.options.disabled&&(d.push([e.isFunction(a.options.items)?a.options.items.call(a.element[0],t,{item:this.currentItem}):e(a.options.items,a.element),a]),this.containers.push(a));for(i=d.length-1;i>=0;i--)for(o=d[i][1],r=d[i][0],s=0,l=r.length;l>s;s++)h=e(r[s]),h.data(this.widgetName+"-item",o),u.push({item:h,instance:o,width:0,height:0,left:0,top:0})},refreshPositions:function(t){this.floating=this.items.length?"x"===this.options.axis||this._isFloating(this.items[0].item):!1,this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,a;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?e(this.options.toleranceElement,s.item):s.item,t||(s.width=n.outerWidth(),s.height=n.outerHeight()),a=n.offset(),s.left=a.left,s.top=a.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)a=this.containers[i].element.offset(),this.containers[i].containerCache.left=a.left,this.containers[i].containerCache.top=a.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(t){t=t||this;var i,s=t.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=t.currentItem[0].nodeName.toLowerCase(),n=e("<"+s+">",t.document[0]).addClass(i||t.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");return"tbody"===s?t._createTrPlaceholder(t.currentItem.find("tr").eq(0),e("<tr>",t.document[0]).appendTo(n)):"tr"===s?t._createTrPlaceholder(t.currentItem,n):"img"===s&&n.attr("src",t.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(e,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(t.currentItem.innerHeight()-parseInt(t.currentItem.css("paddingTop")||0,10)-parseInt(t.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(t.currentItem.innerWidth()-parseInt(t.currentItem.css("paddingLeft")||0,10)-parseInt(t.currentItem.css("paddingRight")||0,10)))}}),t.placeholder=e(s.placeholder.element.call(t.element,t.currentItem)),t.currentItem.after(t.placeholder),s.placeholder.update(t,t.placeholder)},_createTrPlaceholder:function(t,i){var s=this;t.children().each(function(){e("<td>&#160;</td>",s.document[0]).attr("colspan",e(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(t){var i,s,n,a,o,r,h,l,u,d,c=null,p=null;for(i=this.containers.length-1;i>=0;i--)if(!e.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(c&&e.contains(this.containers[i].element[0],c.element[0]))continue;c=this.containers[i],p=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",t,this._uiHash(this)),this.containers[i].containerCache.over=0);if(c)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",t,this._uiHash(this)),this.containers[p].containerCache.over=1);else{for(n=1e4,a=null,u=c.floating||this._isFloating(this.currentItem),o=u?"left":"top",r=u?"width":"height",d=u?"clientX":"clientY",s=this.items.length-1;s>=0;s--)e.contains(this.containers[p].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(h=this.items[s].item.offset()[o],l=!1,t[d]-h>this.items[s][r]/2&&(l=!0),n>Math.abs(t[d]-h)&&(n=Math.abs(t[d]-h),a=this.items[s],this.direction=l?"up":"down"));if(!a&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[p])return this.currentContainer.containerCache.over||(this.containers[p]._trigger("over",t,this._uiHash()),this.currentContainer.containerCache.over=1),void 0;a?this._rearrange(t,a,null,!0):this._rearrange(t,null,this.containers[p].element,!0),this._trigger("change",t,this._uiHash()),this.containers[p]._trigger("change",t,this._uiHash(this)),this.currentContainer=this.containers[p],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[p]._trigger("over",t,this._uiHash(this)),this.containers[p].containerCache.over=1}},_createHelper:function(t){var i=this.options,s=e.isFunction(i.helper)?e(i.helper.apply(this.element[0],[t,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||e("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(t){"string"==typeof t&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&e.ui.ie)&&(t={top:0,left:0}),{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var e=this.currentItem.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===n.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===n.containment?this.document.width():this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(t=e(n.containment)[0],i=e(n.containment).offset(),s="hidden"!==e(t).css("overflow"),this.containment=[i.left+(parseInt(e(t).css("borderLeftWidth"),10)||0)+(parseInt(e(t).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(e(t).css("borderTopWidth"),10)||0)+(parseInt(e(t).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(t.scrollWidth,t.offsetWidth):t.offsetWidth)-(parseInt(e(t).css("borderLeftWidth"),10)||0)-(parseInt(e(t).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(t.scrollHeight,t.offsetHeight):t.offsetHeight)-(parseInt(e(t).css("borderTopWidth"),10)||0)-(parseInt(e(t).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top])},_convertPositionTo:function(t,i){i||(i=this.position);var s="absolute"===t?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,a=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():a?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():a?0:n.scrollLeft())*s}},_generatePosition:function(t){var i,s,n=this.options,a=t.pageX,o=t.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(t.pageX-this.offset.click.left<this.containment[0]&&(a=this.containment[0]+this.offset.click.left),t.pageY-this.offset.click.top<this.containment[1]&&(o=this.containment[1]+this.offset.click.top),t.pageX-this.offset.click.left>this.containment[2]&&(a=this.containment[2]+this.offset.click.left),t.pageY-this.offset.click.top>this.containment[3]&&(o=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1],o=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((a-this.originalPageX)/n.grid[0])*n.grid[0],a=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:o-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:a-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(e,t,i,s){i?i[0].appendChild(this.placeholder[0]):t.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?t.item[0]:t.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(e,t){function i(e,t,i){return function(s){i._trigger(e,s,t._uiHash(t))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!t&&n.push(function(e){this._trigger("receive",e,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||t||n.push(function(e){this._trigger("update",e,this._uiHash())}),this!==this.currentContainer&&(t||(n.push(function(e){this._trigger("remove",e,this._uiHash())}),n.push(function(e){return function(t){e._trigger("receive",t,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(e){return function(t){e._trigger("update",t,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)t||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,t||this._trigger("beforeStop",e,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!t){for(s=0;n.length>s;s++)n[s].call(this,e);this._trigger("stop",e,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){e.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(t){var i=t||this;return{helper:i.helper,placeholder:i.placeholder||e([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:t?t.element:null}}}),e.widget("ui.accordion",{version:"1.11.4",options:{active:0,animate:{},collapsible:!1,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var t=this.options;this.prevShow=this.prevHide=e(),this.element.addClass("ui-accordion ui-widget ui-helper-reset").attr("role","tablist"),t.collapsible||t.active!==!1&&null!=t.active||(t.active=0),this._processPanels(),0>t.active&&(t.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():e()}},_createIcons:function(){var t=this.options.icons;t&&(e("<span>").addClass("ui-accordion-header-icon ui-icon "+t.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(t.header).addClass(t.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var e;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").removeUniqueId(),this._destroyIcons(),e=this.headers.next().removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").css("display","").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&e.css("height","")},_setOption:function(e,t){return"active"===e?(this._activate(t),void 0):("event"===e&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(t)),this._super(e,t),"collapsible"!==e||t||this.options.active!==!1||this._activate(0),"icons"===e&&(this._destroyIcons(),t&&this._createIcons()),"disabled"===e&&(this.element.toggleClass("ui-state-disabled",!!t).attr("aria-disabled",t),this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!t)),void 0)},_keydown:function(t){if(!t.altKey&&!t.ctrlKey){var i=e.ui.keyCode,s=this.headers.length,n=this.headers.index(t.target),a=!1;switch(t.keyCode){case i.RIGHT:case i.DOWN:a=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:a=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(t);break;case i.HOME:a=this.headers[0];break;case i.END:a=this.headers[s-1]}a&&(e(t.target).attr("tabIndex",-1),e(a).attr("tabIndex",0),a.focus(),t.preventDefault())}},_panelKeyDown:function(t){t.keyCode===e.ui.keyCode.UP&&t.ctrlKey&&e(t.currentTarget).prev().focus()},refresh:function(){var t=this.options;this._processPanels(),t.active===!1&&t.collapsible===!0||!this.headers.length?(t.active=!1,this.active=e()):t.active===!1?this._activate(0):this.active.length&&!e.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(t.active=!1,this.active=e()):this._activate(Math.max(0,t.active-1)):t.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var e=this.headers,t=this.panels;this.headers=this.element.find(this.options.header).addClass("ui-accordion-header ui-state-default ui-corner-all"),this.panels=this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").filter(":not(.ui-accordion-content-active)").hide(),t&&(this._off(e.not(this.headers)),this._off(t.not(this.panels)))
+},_refresh:function(){var t,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active).addClass("ui-accordion-header-active ui-state-active ui-corner-top").removeClass("ui-corner-all"),this.active.next().addClass("ui-accordion-content-active").show(),this.headers.attr("role","tab").each(function(){var t=e(this),i=t.uniqueId().attr("id"),s=t.next(),n=s.uniqueId().attr("id");t.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(t=n.height(),this.element.siblings(":visible").each(function(){var i=e(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(t-=i.outerHeight(!0))}),this.headers.each(function(){t-=e(this).outerHeight(!0)}),this.headers.next().each(function(){e(this).height(Math.max(0,t-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):"auto"===s&&(t=0,this.headers.next().each(function(){t=Math.max(t,e(this).css("height","").height())}).height(t))},_activate:function(t){var i=this._findActive(t)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:e.noop}))},_findActive:function(t){return"number"==typeof t?this.headers.eq(t):e()},_setupEvents:function(t){var i={keydown:"_keydown"};t&&e.each(t.split(" "),function(e,t){i[t]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(t){var i=this.options,s=this.active,n=e(t.currentTarget),a=n[0]===s[0],o=a&&i.collapsible,r=o?e():n.next(),h=s.next(),l={oldHeader:s,oldPanel:h,newHeader:o?e():n,newPanel:r};t.preventDefault(),a&&!i.collapsible||this._trigger("beforeActivate",t,l)===!1||(i.active=o?!1:this.headers.index(n),this.active=a?e():n,this._toggle(l),s.removeClass("ui-accordion-header-active ui-state-active"),i.icons&&s.children(".ui-accordion-header-icon").removeClass(i.icons.activeHeader).addClass(i.icons.header),a||(n.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),i.icons&&n.children(".ui-accordion-header-icon").removeClass(i.icons.header).addClass(i.icons.activeHeader),n.next().addClass("ui-accordion-content-active")))},_toggle:function(t){var i=t.newPanel,s=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,t):(s.hide(),i.show(),this._toggleComplete(t)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(e(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(e,t,i){var s,n,a,o=this,r=0,h=e.css("box-sizing"),l=e.length&&(!t.length||e.index()<t.index()),u=this.options.animate||{},d=l&&u.down||u,c=function(){o._toggleComplete(i)};return"number"==typeof d&&(a=d),"string"==typeof d&&(n=d),n=n||d.easing||u.easing,a=a||d.duration||u.duration,t.length?e.length?(s=e.show().outerHeight(),t.animate(this.hideProps,{duration:a,easing:n,step:function(e,t){t.now=Math.round(e)}}),e.hide().animate(this.showProps,{duration:a,easing:n,complete:c,step:function(e,i){i.now=Math.round(e),"height"!==i.prop?"content-box"===h&&(r+=i.now):"content"!==o.options.heightStyle&&(i.now=Math.round(s-t.outerHeight()-r),r=0)}}),void 0):t.animate(this.hideProps,a,n,c):e.animate(this.showProps,a,n,c)},_toggleComplete:function(e){var t=e.oldPanel;t.removeClass("ui-accordion-content-active").prev().removeClass("ui-corner-top").addClass("ui-corner-all"),t.length&&(t.parent()[0].className=t.parent()[0].className),this._trigger("activate",null,e)}}),e.widget("ui.menu",{version:"1.11.4",defaultElement:"<ul>",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},items:"> *",menus:"ul",position:{my:"left-1 top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item":function(e){e.preventDefault()},"click .ui-menu-item":function(t){var i=e(t.target);!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(t),t.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(t):!this.element.is(":focus")&&e(this.document[0].activeElement).closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(t){if(!this.previousFilter){var i=e(t.currentTarget);i.siblings(".ui-state-active").removeClass("ui-state-active"),this.focus(t,i)}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(e,t){var i=this.active||this.element.find(this.options.items).eq(0);t||this.focus(e,i)},blur:function(t){this._delay(function(){e.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(e){this._closeOnDocumentClick(e)&&this.collapseAll(e),this.mouseHandled=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-menu-icons ui-front").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").removeUniqueId().removeClass("ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var t=e(this);t.data("ui-menu-submenu-carat")&&t.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(t){var i,s,n,a,o=!0;switch(t.keyCode){case e.ui.keyCode.PAGE_UP:this.previousPage(t);break;case e.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case e.ui.keyCode.HOME:this._move("first","first",t);break;case e.ui.keyCode.END:this._move("last","last",t);break;case e.ui.keyCode.UP:this.previous(t);break;case e.ui.keyCode.DOWN:this.next(t);break;case e.ui.keyCode.LEFT:this.collapse(t);break;case e.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case e.ui.keyCode.ENTER:case e.ui.keyCode.SPACE:this._activate(t);break;case e.ui.keyCode.ESCAPE:this.collapse(t);break;default:o=!1,s=this.previousFilter||"",n=String.fromCharCode(t.keyCode),a=!1,clearTimeout(this.filterTimer),n===s?a=!0:n=s+n,i=this._filterMenuItems(n),i=a&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(t.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(t,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}o&&t.preventDefault()},_activate:function(e){this.active.is(".ui-state-disabled")||(this.active.is("[aria-haspopup='true']")?this.expand(e):this.select(e))},refresh:function(){var t,i,s=this,n=this.options.icons.submenu,a=this.element.find(this.options.menus);this.element.toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length),a.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-front").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=e(this),i=t.parent(),s=e("<span>").addClass("ui-menu-icon ui-icon "+n).data("ui-menu-submenu-carat",!0);i.attr("aria-haspopup","true").prepend(s),t.attr("aria-labelledby",i.attr("id"))}),t=a.add(this.element),i=t.find(this.options.items),i.not(".ui-menu-item").each(function(){var t=e(this);s._isDivider(t)&&t.addClass("ui-widget-content ui-menu-divider")}),i.not(".ui-menu-item, .ui-menu-divider").addClass("ui-menu-item").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!e.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(e,t){"icons"===e&&this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(t.submenu),"disabled"===e&&this.element.toggleClass("ui-state-disabled",!!t).attr("aria-disabled",t),this._super(e,t)},focus:function(e,t){var i,s;this.blur(e,e&&"focus"===e.type),this._scrollIntoView(t),this.active=t.first(),s=this.active.addClass("ui-state-focus").removeClass("ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),this.active.parent().closest(".ui-menu-item").addClass("ui-state-active"),e&&"keydown"===e.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=t.children(".ui-menu"),i.length&&e&&/^mouse/.test(e.type)&&this._startOpening(i),this.activeMenu=t.parent(),this._trigger("focus",e,{item:t})},_scrollIntoView:function(t){var i,s,n,a,o,r;this._hasScroll()&&(i=parseFloat(e.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(e.css(this.activeMenu[0],"paddingTop"))||0,n=t.offset().top-this.activeMenu.offset().top-i-s,a=this.activeMenu.scrollTop(),o=this.activeMenu.height(),r=t.outerHeight(),0>n?this.activeMenu.scrollTop(a+n):n+r>o&&this.activeMenu.scrollTop(a+n-o+r))},blur:function(e,t){t||clearTimeout(this.timer),this.active&&(this.active.removeClass("ui-state-focus"),this.active=null,this._trigger("blur",e,{item:this.active}))},_startOpening:function(e){clearTimeout(this.timer),"true"===e.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(e)},this.delay))},_open:function(t){var i=e.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(t.parents(".ui-menu")).hide().attr("aria-hidden","true"),t.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(t,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:e(t&&t.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(t),this.activeMenu=s},this.delay)},_close:function(e){e||(e=this.active?this.active.parent():this.element),e.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find(".ui-state-active").not(".ui-state-focus").removeClass("ui-state-active")},_closeOnDocumentClick:function(t){return!e(t.target).closest(".ui-menu").length},_isDivider:function(e){return!/[^\-\u2014\u2013\s]/.test(e.text())},collapse:function(e){var t=this.active&&this.active.parent().closest(".ui-menu-item",this.element);t&&t.length&&(this._close(),this.focus(e,t))},expand:function(e){var t=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();t&&t.length&&(this._open(t.parent()),this._delay(function(){this.focus(e,t)}))},next:function(e){this._move("next","first",e)},previous:function(e){this._move("prev","last",e)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(e,t,i){var s;this.active&&(s="first"===e||"last"===e?this.active["first"===e?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[e+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[t]()),this.focus(i,s)},nextPage:function(t){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=e(this),0>i.offset().top-s-n}),this.focus(t,i)):this.focus(t,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(t),void 0)},previousPage:function(t){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=e(this),i.offset().top-s+n>0}),this.focus(t,i)):this.focus(t,this.activeMenu.find(this.options.items).first())),void 0):(this.next(t),void 0)},_hasScroll:function(){return this.element.outerHeight()<this.element.prop("scrollHeight")},select:function(t){this.active=this.active||e(t.target).closest(".ui-menu-item");var i={item:this.active};this.active.has(".ui-menu").length||this.collapseAll(t,!0),this._trigger("select",t,i)},_filterMenuItems:function(t){var i=t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&"),s=RegExp("^"+i,"i");return this.activeMenu.find(this.options.items).filter(".ui-menu-item").filter(function(){return s.test(e.trim(e(this).text()))})}}),e.widget("ui.autocomplete",{version:"1.11.4",defaultElement:"<input>",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var t,i,s,n=this.element[0].nodeName.toLowerCase(),a="textarea"===n,o="input"===n;this.isMultiLine=a?!0:o?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[a||o?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return t=!0,s=!0,i=!0,void 0;t=!1,s=!1,i=!1;var a=e.ui.keyCode;switch(n.keyCode){case a.PAGE_UP:t=!0,this._move("previousPage",n);break;case a.PAGE_DOWN:t=!0,this._move("nextPage",n);break;case a.UP:t=!0,this._keyEvent("previous",n);break;case a.DOWN:t=!0,this._keyEvent("next",n);break;case a.ENTER:this.menu.active&&(t=!0,n.preventDefault(),this.menu.select(n));break;case a.TAB:this.menu.active&&this.menu.select(n);break;case a.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(t)return t=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=e.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(e){return s?(s=!1,e.preventDefault(),void 0):(this._searchTimeout(e),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(e),this._change(e),void 0)}}),this._initSource(),this.menu=e("<ul>").addClass("ui-autocomplete ui-front").appendTo(this._appendTo()).menu({role:null}).hide().menu("instance"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var i=this.menu.element[0];e(t.target).closest(".ui-menu-item").length||this._delay(function(){var t=this;this.document.one("mousedown",function(s){s.target===t.element[0]||s.target===i||e.contains(i,s.target)||t.close()})})},menufocus:function(t,i){var s,n;return this.isNewMenu&&(this.isNewMenu=!1,t.originalEvent&&/^mouse/.test(t.originalEvent.type))?(this.menu.blur(),this.document.one("mousemove",function(){e(t.target).trigger(t.originalEvent)}),void 0):(n=i.item.data("ui-autocomplete-item"),!1!==this._trigger("focus",t,{item:n})&&t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(n.value),s=i.item.attr("aria-label")||n.value,s&&e.trim(s).length&&(this.liveRegion.children().hide(),e("<div>").text(s).appendTo(this.liveRegion)),void 0)},menuselect:function(e,t){var i=t.item.data("ui-autocomplete-item"),s=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s,this.selectedItem=i})),!1!==this._trigger("select",e,{item:i})&&this._value(i.value),this.term=this._value(),this.close(e),this.selectedItem=i}}),this.liveRegion=e("<span>",{role:"status","aria-live":"assertive","aria-relevant":"additions"}).addClass("ui-helper-hidden-accessible").appendTo(this.document[0].body),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),"source"===e&&this._initSource(),"appendTo"===e&&this.menu.element.appendTo(this._appendTo()),"disabled"===e&&t&&this.xhr&&this.xhr.abort()},_appendTo:function(){var t=this.options.appendTo;return t&&(t=t.jquery||t.nodeType?e(t):this.document.find(t).eq(0)),t&&t[0]||(t=this.element.closest(".ui-front")),t.length||(t=this.document[0].body),t},_initSource:function(){var t,i,s=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(i,s){s(e.ui.autocomplete.filter(t,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(t,n){s.xhr&&s.xhr.abort(),s.xhr=e.ajax({url:i,data:t,dataType:"json",success:function(e){n(e)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){var t=this.term===this._value(),i=this.menu.element.is(":visible"),s=e.altKey||e.ctrlKey||e.metaKey||e.shiftKey;(!t||t&&!i&&!s)&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){return e=null!=e?e:this._value(),this.term=this._value(),e.length<this.options.minLength?this.close(t):this._trigger("search",t)!==!1?this._search(e):void 0},_search:function(e){this.pending++,this.element.addClass("ui-autocomplete-loading"),this.cancelSearch=!1,this.source({term:e},this._response())},_response:function(){var t=++this.requestIndex;return e.proxy(function(e){t===this.requestIndex&&this.__response(e),this.pending--,this.pending||this.element.removeClass("ui-autocomplete-loading")},this)},__response:function(e){e&&(e=this._normalize(e)),this._trigger("response",null,{content:e}),!this.options.disabled&&e&&e.length&&!this.cancelSearch?(this._suggest(e),this._trigger("open")):this._close()},close:function(e){this.cancelSearch=!0,this._close(e)},_close:function(e){this.menu.element.is(":visible")&&(this.menu.element.hide(),this.menu.blur(),this.isNewMenu=!0,this._trigger("close",e))},_change:function(e){this.previous!==this._value()&&this._trigger("change",e,{item:this.selectedItem})},_normalize:function(t){return t.length&&t[0].label&&t[0].value?t:e.map(t,function(t){return"string"==typeof t?{label:t,value:t}:e.extend({},t,{label:t.label||t.value,value:t.value||t.label})})},_suggest:function(t){var i=this.menu.element.empty();this._renderMenu(i,t),this.isNewMenu=!0,this.menu.refresh(),i.show(),this._resizeMenu(),i.position(e.extend({of:this.element},this.options.position)),this.options.autoFocus&&this.menu.next()},_resizeMenu:function(){var e=this.menu.element;e.outerWidth(Math.max(e.width("").outerWidth()+1,this.element.outerWidth()))},_renderMenu:function(t,i){var s=this;e.each(i,function(e,i){s._renderItemData(t,i)})},_renderItemData:function(e,t){return this._renderItem(e,t).data("ui-autocomplete-item",t)},_renderItem:function(t,i){return e("<li>").text(i.label).appendTo(t)},_move:function(e,t){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)?(this.isMultiLine||this._value(this.term),this.menu.blur(),void 0):(this.menu[e](t),void 0):(this.search(null,t),void 0)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(e,t),t.preventDefault())}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(t,i){var s=RegExp(e.ui.autocomplete.escapeRegex(i),"i");return e.grep(t,function(e){return s.test(e.label||e.value||e)})}}),e.widget("ui.autocomplete",e.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(e){return e+(e>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(t){var i;this._superApply(arguments),this.options.disabled||this.cancelSearch||(i=t&&t.length?this.options.messages.results(t.length):this.options.messages.noResults,this.liveRegion.children().hide(),e("<div>").text(i).appendTo(this.liveRegion))}}),e.ui.autocomplete;var c,p="ui-button ui-widget ui-state-default ui-corner-all",f="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",m=function(){var t=e(this);setTimeout(function(){t.find(":ui-button").button("refresh")},1)},g=function(t){var i=t.name,s=t.form,n=e([]);return i&&(i=i.replace(/'/g,"\\'"),n=s?e(s).find("[name='"+i+"'][type=radio]"):e("[name='"+i+"'][type=radio]",t.ownerDocument).filter(function(){return!this.form})),n};e.widget("ui.button",{version:"1.11.4",defaultElement:"<button>",options:{disabled:null,text:!0,label:null,icons:{primary:null,secondary:null}},_create:function(){this.element.closest("form").unbind("reset"+this.eventNamespace).bind("reset"+this.eventNamespace,m),"boolean"!=typeof this.options.disabled?this.options.disabled=!!this.element.prop("disabled"):this.element.prop("disabled",this.options.disabled),this._determineButtonType(),this.hasTitle=!!this.buttonElement.attr("title");var t=this,i=this.options,s="checkbox"===this.type||"radio"===this.type,n=s?"":"ui-state-active";null===i.label&&(i.label="input"===this.type?this.buttonElement.val():this.buttonElement.html()),this._hoverable(this.buttonElement),this.buttonElement.addClass(p).attr("role","button").bind("mouseenter"+this.eventNamespace,function(){i.disabled||this===c&&e(this).addClass("ui-state-active")}).bind("mouseleave"+this.eventNamespace,function(){i.disabled||e(this).removeClass(n)}).bind("click"+this.eventNamespace,function(e){i.disabled&&(e.preventDefault(),e.stopImmediatePropagation())}),this._on({focus:function(){this.buttonElement.addClass("ui-state-focus")},blur:function(){this.buttonElement.removeClass("ui-state-focus")}}),s&&this.element.bind("change"+this.eventNamespace,function(){t.refresh()}),"checkbox"===this.type?this.buttonElement.bind("click"+this.eventNamespace,function(){return i.disabled?!1:void 0}):"radio"===this.type?this.buttonElement.bind("click"+this.eventNamespace,function(){if(i.disabled)return!1;e(this).addClass("ui-state-active"),t.buttonElement.attr("aria-pressed","true");var s=t.element[0];g(s).not(s).map(function(){return e(this).button("widget")[0]}).removeClass("ui-state-active").attr("aria-pressed","false")}):(this.buttonElement.bind("mousedown"+this.eventNamespace,function(){return i.disabled?!1:(e(this).addClass("ui-state-active"),c=this,t.document.one("mouseup",function(){c=null}),void 0)}).bind("mouseup"+this.eventNamespace,function(){return i.disabled?!1:(e(this).removeClass("ui-state-active"),void 0)}).bind("keydown"+this.eventNamespace,function(t){return i.disabled?!1:((t.keyCode===e.ui.keyCode.SPACE||t.keyCode===e.ui.keyCode.ENTER)&&e(this).addClass("ui-state-active"),void 0)}).bind("keyup"+this.eventNamespace+" blur"+this.eventNamespace,function(){e(this).removeClass("ui-state-active")}),this.buttonElement.is("a")&&this.buttonElement.keyup(function(t){t.keyCode===e.ui.keyCode.SPACE&&e(this).click()})),this._setOption("disabled",i.disabled),this._resetButton()},_determineButtonType:function(){var e,t,i;this.type=this.element.is("[type=checkbox]")?"checkbox":this.element.is("[type=radio]")?"radio":this.element.is("input")?"input":"button","checkbox"===this.type||"radio"===this.type?(e=this.element.parents().last(),t="label[for='"+this.element.attr("id")+"']",this.buttonElement=e.find(t),this.buttonElement.length||(e=e.length?e.siblings():this.element.siblings(),this.buttonElement=e.filter(t),this.buttonElement.length||(this.buttonElement=e.find(t))),this.element.addClass("ui-helper-hidden-accessible"),i=this.element.is(":checked"),i&&this.buttonElement.addClass("ui-state-active"),this.buttonElement.prop("aria-pressed",i)):this.buttonElement=this.element},widget:function(){return this.buttonElement},_destroy:function(){this.element.removeClass("ui-helper-hidden-accessible"),this.buttonElement.removeClass(p+" ui-state-active "+f).removeAttr("role").removeAttr("aria-pressed").html(this.buttonElement.find(".ui-button-text").html()),this.hasTitle||this.buttonElement.removeAttr("title")},_setOption:function(e,t){return this._super(e,t),"disabled"===e?(this.widget().toggleClass("ui-state-disabled",!!t),this.element.prop("disabled",!!t),t&&("checkbox"===this.type||"radio"===this.type?this.buttonElement.removeClass("ui-state-focus"):this.buttonElement.removeClass("ui-state-focus ui-state-active")),void 0):(this._resetButton(),void 0)},refresh:function(){var t=this.element.is("input, button")?this.element.is(":disabled"):this.element.hasClass("ui-button-disabled");t!==this.options.disabled&&this._setOption("disabled",t),"radio"===this.type?g(this.element[0]).each(function(){e(this).is(":checked")?e(this).button("widget").addClass("ui-state-active").attr("aria-pressed","true"):e(this).button("widget").removeClass("ui-state-active").attr("aria-pressed","false")}):"checkbox"===this.type&&(this.element.is(":checked")?this.buttonElement.addClass("ui-state-active").attr("aria-pressed","true"):this.buttonElement.removeClass("ui-state-active").attr("aria-pressed","false"))},_resetButton:function(){if("input"===this.type)return this.options.label&&this.element.val(this.options.label),void 0;var t=this.buttonElement.removeClass(f),i=e("<span></span>",this.document[0]).addClass("ui-button-text").html(this.options.label).appendTo(t.empty()).text(),s=this.options.icons,n=s.primary&&s.secondary,a=[];s.primary||s.secondary?(this.options.text&&a.push("ui-button-text-icon"+(n?"s":s.primary?"-primary":"-secondary")),s.primary&&t.prepend("<span class='ui-button-icon-primary ui-icon "+s.primary+"'></span>"),s.secondary&&t.append("<span class='ui-button-icon-secondary ui-icon "+s.secondary+"'></span>"),this.options.text||(a.push(n?"ui-button-icons-only":"ui-button-icon-only"),this.hasTitle||t.attr("title",e.trim(i)))):a.push("ui-button-text-only"),t.addClass(a.join(" "))}}),e.widget("ui.buttonset",{version:"1.11.4",options:{items:"button, input[type=button], input[type=submit], input[type=reset], input[type=checkbox], input[type=radio], a, :data(ui-button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(e,t){"disabled"===e&&this.buttons.button("option",e,t),this._super(e,t)},refresh:function(){var t="rtl"===this.element.css("direction"),i=this.element.find(this.options.items),s=i.filter(":ui-button");i.not(":ui-button").button(),s.button("refresh"),this.buttons=i.map(function(){return e(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(t?"ui-corner-right":"ui-corner-left").end().filter(":last").addClass(t?"ui-corner-left":"ui-corner-right").end().end()},_destroy:function(){this.element.removeClass("ui-buttonset"),this.buttons.map(function(){return e(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy")}}),e.ui.button,e.extend(e.ui,{datepicker:{version:"1.11.4"}});var v;e.extend(n.prototype,{markerClassName:"hasDatepicker",maxRows:4,_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(e){return r(this._defaults,e||{}),this},_attachDatepicker:function(t,i){var s,n,a;s=t.nodeName.toLowerCase(),n="div"===s||"span"===s,t.id||(this.uuid+=1,t.id="dp"+this.uuid),a=this._newInst(e(t),n),a.settings=e.extend({},i||{}),"input"===s?this._connectDatepicker(t,a):n&&this._inlineDatepicker(t,a)},_newInst:function(t,i){var s=t[0].id.replace(/([^A-Za-z0-9_\-])/g,"\\\\$1");return{id:s,input:t,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:i,dpDiv:i?a(e("<div class='"+this._inlineClass+" ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")):this.dpDiv}},_connectDatepicker:function(t,i){var s=e(t);i.append=e([]),i.trigger=e([]),s.hasClass(this.markerClassName)||(this._attachments(s,i),s.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp),this._autoSize(i),e.data(t,"datepicker",i),i.settings.disabled&&this._disableDatepicker(t))},_attachments:function(t,i){var s,n,a,o=this._get(i,"appendText"),r=this._get(i,"isRTL");i.append&&i.append.remove(),o&&(i.append=e("<span class='"+this._appendClass+"'>"+o+"</span>"),t[r?"before":"after"](i.append)),t.unbind("focus",this._showDatepicker),i.trigger&&i.trigger.remove(),s=this._get(i,"showOn"),("focus"===s||"both"===s)&&t.focus(this._showDatepicker),("button"===s||"both"===s)&&(n=this._get(i,"buttonText"),a=this._get(i,"buttonImage"),i.trigger=e(this._get(i,"buttonImageOnly")?e("<img/>").addClass(this._triggerClass).attr({src:a,alt:n,title:n}):e("<button type='button'></button>").addClass(this._triggerClass).html(a?e("<img/>").attr({src:a,alt:n,title:n}):n)),t[r?"before":"after"](i.trigger),i.trigger.click(function(){return e.datepicker._datepickerShowing&&e.datepicker._lastInput===t[0]?e.datepicker._hideDatepicker():e.datepicker._datepickerShowing&&e.datepicker._lastInput!==t[0]?(e.datepicker._hideDatepicker(),e.datepicker._showDatepicker(t[0])):e.datepicker._showDatepicker(t[0]),!1}))},_autoSize:function(e){if(this._get(e,"autoSize")&&!e.inline){var t,i,s,n,a=new Date(2009,11,20),o=this._get(e,"dateFormat");o.match(/[DM]/)&&(t=function(e){for(i=0,s=0,n=0;e.length>n;n++)e[n].length>i&&(i=e[n].length,s=n);return s},a.setMonth(t(this._get(e,o.match(/MM/)?"monthNames":"monthNamesShort"))),a.setDate(t(this._get(e,o.match(/DD/)?"dayNames":"dayNamesShort"))+20-a.getDay())),e.input.attr("size",this._formatDate(e,a).length)}},_inlineDatepicker:function(t,i){var s=e(t);s.hasClass(this.markerClassName)||(s.addClass(this.markerClassName).append(i.dpDiv),e.data(t,"datepicker",i),this._setDate(i,this._getDefaultDate(i),!0),this._updateDatepicker(i),this._updateAlternate(i),i.settings.disabled&&this._disableDatepicker(t),i.dpDiv.css("display","block"))},_dialogDatepicker:function(t,i,s,n,a){var o,h,l,u,d,c=this._dialogInst;return c||(this.uuid+=1,o="dp"+this.uuid,this._dialogInput=e("<input type='text' id='"+o+"' style='position: absolute; top: -100px; width: 0px;'/>"),this._dialogInput.keydown(this._doKeyDown),e("body").append(this._dialogInput),c=this._dialogInst=this._newInst(this._dialogInput,!1),c.settings={},e.data(this._dialogInput[0],"datepicker",c)),r(c.settings,n||{}),i=i&&i.constructor===Date?this._formatDate(c,i):i,this._dialogInput.val(i),this._pos=a?a.length?a:[a.pageX,a.pageY]:null,this._pos||(h=document.documentElement.clientWidth,l=document.documentElement.clientHeight,u=document.documentElement.scrollLeft||document.body.scrollLeft,d=document.documentElement.scrollTop||document.body.scrollTop,this._pos=[h/2-100+u,l/2-150+d]),this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),c.settings.onSelect=s,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),e.blockUI&&e.blockUI(this.dpDiv),e.data(this._dialogInput[0],"datepicker",c),this
+},_destroyDatepicker:function(t){var i,s=e(t),n=e.data(t,"datepicker");s.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),e.removeData(t,"datepicker"),"input"===i?(n.append.remove(),n.trigger.remove(),s.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):("div"===i||"span"===i)&&s.removeClass(this.markerClassName).empty(),v===n&&(v=null))},_enableDatepicker:function(t){var i,s,n=e(t),a=e.data(t,"datepicker");n.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),"input"===i?(t.disabled=!1,a.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().removeClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)),this._disabledInputs=e.map(this._disabledInputs,function(e){return e===t?null:e}))},_disableDatepicker:function(t){var i,s,n=e(t),a=e.data(t,"datepicker");n.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),"input"===i?(t.disabled=!0,a.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().addClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)),this._disabledInputs=e.map(this._disabledInputs,function(e){return e===t?null:e}),this._disabledInputs[this._disabledInputs.length]=t)},_isDisabledDatepicker:function(e){if(!e)return!1;for(var t=0;this._disabledInputs.length>t;t++)if(this._disabledInputs[t]===e)return!0;return!1},_getInst:function(t){try{return e.data(t,"datepicker")}catch(i){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(t,i,s){var n,a,o,h,l=this._getInst(t);return 2===arguments.length&&"string"==typeof i?"defaults"===i?e.extend({},e.datepicker._defaults):l?"all"===i?e.extend({},l.settings):this._get(l,i):null:(n=i||{},"string"==typeof i&&(n={},n[i]=s),l&&(this._curInst===l&&this._hideDatepicker(),a=this._getDateDatepicker(t,!0),o=this._getMinMaxDate(l,"min"),h=this._getMinMaxDate(l,"max"),r(l.settings,n),null!==o&&void 0!==n.dateFormat&&void 0===n.minDate&&(l.settings.minDate=this._formatDate(l,o)),null!==h&&void 0!==n.dateFormat&&void 0===n.maxDate&&(l.settings.maxDate=this._formatDate(l,h)),"disabled"in n&&(n.disabled?this._disableDatepicker(t):this._enableDatepicker(t)),this._attachments(e(t),l),this._autoSize(l),this._setDate(l,a),this._updateAlternate(l),this._updateDatepicker(l)),void 0)},_changeDatepicker:function(e,t,i){this._optionDatepicker(e,t,i)},_refreshDatepicker:function(e){var t=this._getInst(e);t&&this._updateDatepicker(t)},_setDateDatepicker:function(e,t){var i=this._getInst(e);i&&(this._setDate(i,t),this._updateDatepicker(i),this._updateAlternate(i))},_getDateDatepicker:function(e,t){var i=this._getInst(e);return i&&!i.inline&&this._setDateFromField(i,t),i?this._getDate(i):null},_doKeyDown:function(t){var i,s,n,a=e.datepicker._getInst(t.target),o=!0,r=a.dpDiv.is(".ui-datepicker-rtl");if(a._keyEvent=!0,e.datepicker._datepickerShowing)switch(t.keyCode){case 9:e.datepicker._hideDatepicker(),o=!1;break;case 13:return n=e("td."+e.datepicker._dayOverClass+":not(."+e.datepicker._currentClass+")",a.dpDiv),n[0]&&e.datepicker._selectDay(t.target,a.selectedMonth,a.selectedYear,n[0]),i=e.datepicker._get(a,"onSelect"),i?(s=e.datepicker._formatDate(a),i.apply(a.input?a.input[0]:null,[s,a])):e.datepicker._hideDatepicker(),!1;case 27:e.datepicker._hideDatepicker();break;case 33:e.datepicker._adjustDate(t.target,t.ctrlKey?-e.datepicker._get(a,"stepBigMonths"):-e.datepicker._get(a,"stepMonths"),"M");break;case 34:e.datepicker._adjustDate(t.target,t.ctrlKey?+e.datepicker._get(a,"stepBigMonths"):+e.datepicker._get(a,"stepMonths"),"M");break;case 35:(t.ctrlKey||t.metaKey)&&e.datepicker._clearDate(t.target),o=t.ctrlKey||t.metaKey;break;case 36:(t.ctrlKey||t.metaKey)&&e.datepicker._gotoToday(t.target),o=t.ctrlKey||t.metaKey;break;case 37:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,r?1:-1,"D"),o=t.ctrlKey||t.metaKey,t.originalEvent.altKey&&e.datepicker._adjustDate(t.target,t.ctrlKey?-e.datepicker._get(a,"stepBigMonths"):-e.datepicker._get(a,"stepMonths"),"M");break;case 38:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,-7,"D"),o=t.ctrlKey||t.metaKey;break;case 39:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,r?-1:1,"D"),o=t.ctrlKey||t.metaKey,t.originalEvent.altKey&&e.datepicker._adjustDate(t.target,t.ctrlKey?+e.datepicker._get(a,"stepBigMonths"):+e.datepicker._get(a,"stepMonths"),"M");break;case 40:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,7,"D"),o=t.ctrlKey||t.metaKey;break;default:o=!1}else 36===t.keyCode&&t.ctrlKey?e.datepicker._showDatepicker(this):o=!1;o&&(t.preventDefault(),t.stopPropagation())},_doKeyPress:function(t){var i,s,n=e.datepicker._getInst(t.target);return e.datepicker._get(n,"constrainInput")?(i=e.datepicker._possibleChars(e.datepicker._get(n,"dateFormat")),s=String.fromCharCode(null==t.charCode?t.keyCode:t.charCode),t.ctrlKey||t.metaKey||" ">s||!i||i.indexOf(s)>-1):void 0},_doKeyUp:function(t){var i,s=e.datepicker._getInst(t.target);if(s.input.val()!==s.lastVal)try{i=e.datepicker.parseDate(e.datepicker._get(s,"dateFormat"),s.input?s.input.val():null,e.datepicker._getFormatConfig(s)),i&&(e.datepicker._setDateFromField(s),e.datepicker._updateAlternate(s),e.datepicker._updateDatepicker(s))}catch(n){}return!0},_showDatepicker:function(t){if(t=t.target||t,"input"!==t.nodeName.toLowerCase()&&(t=e("input",t.parentNode)[0]),!e.datepicker._isDisabledDatepicker(t)&&e.datepicker._lastInput!==t){var i,n,a,o,h,l,u;i=e.datepicker._getInst(t),e.datepicker._curInst&&e.datepicker._curInst!==i&&(e.datepicker._curInst.dpDiv.stop(!0,!0),i&&e.datepicker._datepickerShowing&&e.datepicker._hideDatepicker(e.datepicker._curInst.input[0])),n=e.datepicker._get(i,"beforeShow"),a=n?n.apply(t,[t,i]):{},a!==!1&&(r(i.settings,a),i.lastVal=null,e.datepicker._lastInput=t,e.datepicker._setDateFromField(i),e.datepicker._inDialog&&(t.value=""),e.datepicker._pos||(e.datepicker._pos=e.datepicker._findPos(t),e.datepicker._pos[1]+=t.offsetHeight),o=!1,e(t).parents().each(function(){return o|="fixed"===e(this).css("position"),!o}),h={left:e.datepicker._pos[0],top:e.datepicker._pos[1]},e.datepicker._pos=null,i.dpDiv.empty(),i.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),e.datepicker._updateDatepicker(i),h=e.datepicker._checkOffset(i,h,o),i.dpDiv.css({position:e.datepicker._inDialog&&e.blockUI?"static":o?"fixed":"absolute",display:"none",left:h.left+"px",top:h.top+"px"}),i.inline||(l=e.datepicker._get(i,"showAnim"),u=e.datepicker._get(i,"duration"),i.dpDiv.css("z-index",s(e(t))+1),e.datepicker._datepickerShowing=!0,e.effects&&e.effects.effect[l]?i.dpDiv.show(l,e.datepicker._get(i,"showOptions"),u):i.dpDiv[l||"show"](l?u:null),e.datepicker._shouldFocusInput(i)&&i.input.focus(),e.datepicker._curInst=i))}},_updateDatepicker:function(t){this.maxRows=4,v=t,t.dpDiv.empty().append(this._generateHTML(t)),this._attachHandlers(t);var i,s=this._getNumberOfMonths(t),n=s[1],a=17,r=t.dpDiv.find("."+this._dayOverClass+" a");r.length>0&&o.apply(r.get(0)),t.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),n>1&&t.dpDiv.addClass("ui-datepicker-multi-"+n).css("width",a*n+"em"),t.dpDiv[(1!==s[0]||1!==s[1]?"add":"remove")+"Class"]("ui-datepicker-multi"),t.dpDiv[(this._get(t,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),t===e.datepicker._curInst&&e.datepicker._datepickerShowing&&e.datepicker._shouldFocusInput(t)&&t.input.focus(),t.yearshtml&&(i=t.yearshtml,setTimeout(function(){i===t.yearshtml&&t.yearshtml&&t.dpDiv.find("select.ui-datepicker-year:first").replaceWith(t.yearshtml),i=t.yearshtml=null},0))},_shouldFocusInput:function(e){return e.input&&e.input.is(":visible")&&!e.input.is(":disabled")&&!e.input.is(":focus")},_checkOffset:function(t,i,s){var n=t.dpDiv.outerWidth(),a=t.dpDiv.outerHeight(),o=t.input?t.input.outerWidth():0,r=t.input?t.input.outerHeight():0,h=document.documentElement.clientWidth+(s?0:e(document).scrollLeft()),l=document.documentElement.clientHeight+(s?0:e(document).scrollTop());return i.left-=this._get(t,"isRTL")?n-o:0,i.left-=s&&i.left===t.input.offset().left?e(document).scrollLeft():0,i.top-=s&&i.top===t.input.offset().top+r?e(document).scrollTop():0,i.left-=Math.min(i.left,i.left+n>h&&h>n?Math.abs(i.left+n-h):0),i.top-=Math.min(i.top,i.top+a>l&&l>a?Math.abs(a+r):0),i},_findPos:function(t){for(var i,s=this._getInst(t),n=this._get(s,"isRTL");t&&("hidden"===t.type||1!==t.nodeType||e.expr.filters.hidden(t));)t=t[n?"previousSibling":"nextSibling"];return i=e(t).offset(),[i.left,i.top]},_hideDatepicker:function(t){var i,s,n,a,o=this._curInst;!o||t&&o!==e.data(t,"datepicker")||this._datepickerShowing&&(i=this._get(o,"showAnim"),s=this._get(o,"duration"),n=function(){e.datepicker._tidyDialog(o)},e.effects&&(e.effects.effect[i]||e.effects[i])?o.dpDiv.hide(i,e.datepicker._get(o,"showOptions"),s,n):o.dpDiv["slideDown"===i?"slideUp":"fadeIn"===i?"fadeOut":"hide"](i?s:null,n),i||n(),this._datepickerShowing=!1,a=this._get(o,"onClose"),a&&a.apply(o.input?o.input[0]:null,[o.input?o.input.val():"",o]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),e.blockUI&&(e.unblockUI(),e("body").append(this.dpDiv))),this._inDialog=!1)},_tidyDialog:function(e){e.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(t){if(e.datepicker._curInst){var i=e(t.target),s=e.datepicker._getInst(i[0]);(i[0].id!==e.datepicker._mainDivId&&0===i.parents("#"+e.datepicker._mainDivId).length&&!i.hasClass(e.datepicker.markerClassName)&&!i.closest("."+e.datepicker._triggerClass).length&&e.datepicker._datepickerShowing&&(!e.datepicker._inDialog||!e.blockUI)||i.hasClass(e.datepicker.markerClassName)&&e.datepicker._curInst!==s)&&e.datepicker._hideDatepicker()}},_adjustDate:function(t,i,s){var n=e(t),a=this._getInst(n[0]);this._isDisabledDatepicker(n[0])||(this._adjustInstDate(a,i+("M"===s?this._get(a,"showCurrentAtPos"):0),s),this._updateDatepicker(a))},_gotoToday:function(t){var i,s=e(t),n=this._getInst(s[0]);this._get(n,"gotoCurrent")&&n.currentDay?(n.selectedDay=n.currentDay,n.drawMonth=n.selectedMonth=n.currentMonth,n.drawYear=n.selectedYear=n.currentYear):(i=new Date,n.selectedDay=i.getDate(),n.drawMonth=n.selectedMonth=i.getMonth(),n.drawYear=n.selectedYear=i.getFullYear()),this._notifyChange(n),this._adjustDate(s)},_selectMonthYear:function(t,i,s){var n=e(t),a=this._getInst(n[0]);a["selected"+("M"===s?"Month":"Year")]=a["draw"+("M"===s?"Month":"Year")]=parseInt(i.options[i.selectedIndex].value,10),this._notifyChange(a),this._adjustDate(n)},_selectDay:function(t,i,s,n){var a,o=e(t);e(n).hasClass(this._unselectableClass)||this._isDisabledDatepicker(o[0])||(a=this._getInst(o[0]),a.selectedDay=a.currentDay=e("a",n).html(),a.selectedMonth=a.currentMonth=i,a.selectedYear=a.currentYear=s,this._selectDate(t,this._formatDate(a,a.currentDay,a.currentMonth,a.currentYear)))},_clearDate:function(t){var i=e(t);this._selectDate(i,"")},_selectDate:function(t,i){var s,n=e(t),a=this._getInst(n[0]);i=null!=i?i:this._formatDate(a),a.input&&a.input.val(i),this._updateAlternate(a),s=this._get(a,"onSelect"),s?s.apply(a.input?a.input[0]:null,[i,a]):a.input&&a.input.trigger("change"),a.inline?this._updateDatepicker(a):(this._hideDatepicker(),this._lastInput=a.input[0],"object"!=typeof a.input[0]&&a.input.focus(),this._lastInput=null)},_updateAlternate:function(t){var i,s,n,a=this._get(t,"altField");a&&(i=this._get(t,"altFormat")||this._get(t,"dateFormat"),s=this._getDate(t),n=this.formatDate(i,s,this._getFormatConfig(t)),e(a).each(function(){e(this).val(n)}))},noWeekends:function(e){var t=e.getDay();return[t>0&&6>t,""]},iso8601Week:function(e){var t,i=new Date(e.getTime());return i.setDate(i.getDate()+4-(i.getDay()||7)),t=i.getTime(),i.setMonth(0),i.setDate(1),Math.floor(Math.round((t-i)/864e5)/7)+1},parseDate:function(t,i,s){if(null==t||null==i)throw"Invalid arguments";if(i="object"==typeof i?""+i:i+"",""===i)return null;var n,a,o,r,h=0,l=(s?s.shortYearCutoff:null)||this._defaults.shortYearCutoff,u="string"!=typeof l?l:(new Date).getFullYear()%100+parseInt(l,10),d=(s?s.dayNamesShort:null)||this._defaults.dayNamesShort,c=(s?s.dayNames:null)||this._defaults.dayNames,p=(s?s.monthNamesShort:null)||this._defaults.monthNamesShort,f=(s?s.monthNames:null)||this._defaults.monthNames,m=-1,g=-1,v=-1,y=-1,b=!1,_=function(e){var i=t.length>n+1&&t.charAt(n+1)===e;return i&&n++,i},x=function(e){var t=_(e),s="@"===e?14:"!"===e?20:"y"===e&&t?4:"o"===e?3:2,n="y"===e?s:1,a=RegExp("^\\d{"+n+","+s+"}"),o=i.substring(h).match(a);if(!o)throw"Missing number at position "+h;return h+=o[0].length,parseInt(o[0],10)},w=function(t,s,n){var a=-1,o=e.map(_(t)?n:s,function(e,t){return[[t,e]]}).sort(function(e,t){return-(e[1].length-t[1].length)});if(e.each(o,function(e,t){var s=t[1];return i.substr(h,s.length).toLowerCase()===s.toLowerCase()?(a=t[0],h+=s.length,!1):void 0}),-1!==a)return a+1;throw"Unknown name at position "+h},k=function(){if(i.charAt(h)!==t.charAt(n))throw"Unexpected literal at position "+h;h++};for(n=0;t.length>n;n++)if(b)"'"!==t.charAt(n)||_("'")?k():b=!1;else switch(t.charAt(n)){case"d":v=x("d");break;case"D":w("D",d,c);break;case"o":y=x("o");break;case"m":g=x("m");break;case"M":g=w("M",p,f);break;case"y":m=x("y");break;case"@":r=new Date(x("@")),m=r.getFullYear(),g=r.getMonth()+1,v=r.getDate();break;case"!":r=new Date((x("!")-this._ticksTo1970)/1e4),m=r.getFullYear(),g=r.getMonth()+1,v=r.getDate();break;case"'":_("'")?k():b=!0;break;default:k()}if(i.length>h&&(o=i.substr(h),!/^\s+/.test(o)))throw"Extra/unparsed characters found in date: "+o;if(-1===m?m=(new Date).getFullYear():100>m&&(m+=(new Date).getFullYear()-(new Date).getFullYear()%100+(u>=m?0:-100)),y>-1)for(g=1,v=y;;){if(a=this._getDaysInMonth(m,g-1),a>=v)break;g++,v-=a}if(r=this._daylightSavingAdjust(new Date(m,g-1,v)),r.getFullYear()!==m||r.getMonth()+1!==g||r.getDate()!==v)throw"Invalid date";return r},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:1e7*60*60*24*(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925)),formatDate:function(e,t,i){if(!t)return"";var s,n=(i?i.dayNamesShort:null)||this._defaults.dayNamesShort,a=(i?i.dayNames:null)||this._defaults.dayNames,o=(i?i.monthNamesShort:null)||this._defaults.monthNamesShort,r=(i?i.monthNames:null)||this._defaults.monthNames,h=function(t){var i=e.length>s+1&&e.charAt(s+1)===t;return i&&s++,i},l=function(e,t,i){var s=""+t;if(h(e))for(;i>s.length;)s="0"+s;return s},u=function(e,t,i,s){return h(e)?s[t]:i[t]},d="",c=!1;if(t)for(s=0;e.length>s;s++)if(c)"'"!==e.charAt(s)||h("'")?d+=e.charAt(s):c=!1;else switch(e.charAt(s)){case"d":d+=l("d",t.getDate(),2);break;case"D":d+=u("D",t.getDay(),n,a);break;case"o":d+=l("o",Math.round((new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime()-new Date(t.getFullYear(),0,0).getTime())/864e5),3);break;case"m":d+=l("m",t.getMonth()+1,2);break;case"M":d+=u("M",t.getMonth(),o,r);break;case"y":d+=h("y")?t.getFullYear():(10>t.getYear()%100?"0":"")+t.getYear()%100;break;case"@":d+=t.getTime();break;case"!":d+=1e4*t.getTime()+this._ticksTo1970;break;case"'":h("'")?d+="'":c=!0;break;default:d+=e.charAt(s)}return d},_possibleChars:function(e){var t,i="",s=!1,n=function(i){var s=e.length>t+1&&e.charAt(t+1)===i;return s&&t++,s};for(t=0;e.length>t;t++)if(s)"'"!==e.charAt(t)||n("'")?i+=e.charAt(t):s=!1;else switch(e.charAt(t)){case"d":case"m":case"y":case"@":i+="0123456789";break;case"D":case"M":return null;case"'":n("'")?i+="'":s=!0;break;default:i+=e.charAt(t)}return i},_get:function(e,t){return void 0!==e.settings[t]?e.settings[t]:this._defaults[t]},_setDateFromField:function(e,t){if(e.input.val()!==e.lastVal){var i=this._get(e,"dateFormat"),s=e.lastVal=e.input?e.input.val():null,n=this._getDefaultDate(e),a=n,o=this._getFormatConfig(e);try{a=this.parseDate(i,s,o)||n}catch(r){s=t?"":s}e.selectedDay=a.getDate(),e.drawMonth=e.selectedMonth=a.getMonth(),e.drawYear=e.selectedYear=a.getFullYear(),e.currentDay=s?a.getDate():0,e.currentMonth=s?a.getMonth():0,e.currentYear=s?a.getFullYear():0,this._adjustInstDate(e)}},_getDefaultDate:function(e){return this._restrictMinMax(e,this._determineDate(e,this._get(e,"defaultDate"),new Date))},_determineDate:function(t,i,s){var n=function(e){var t=new Date;return t.setDate(t.getDate()+e),t},a=function(i){try{return e.datepicker.parseDate(e.datepicker._get(t,"dateFormat"),i,e.datepicker._getFormatConfig(t))}catch(s){}for(var n=(i.toLowerCase().match(/^c/)?e.datepicker._getDate(t):null)||new Date,a=n.getFullYear(),o=n.getMonth(),r=n.getDate(),h=/([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,l=h.exec(i);l;){switch(l[2]||"d"){case"d":case"D":r+=parseInt(l[1],10);break;case"w":case"W":r+=7*parseInt(l[1],10);break;case"m":case"M":o+=parseInt(l[1],10),r=Math.min(r,e.datepicker._getDaysInMonth(a,o));break;case"y":case"Y":a+=parseInt(l[1],10),r=Math.min(r,e.datepicker._getDaysInMonth(a,o))}l=h.exec(i)}return new Date(a,o,r)},o=null==i||""===i?s:"string"==typeof i?a(i):"number"==typeof i?isNaN(i)?s:n(i):new Date(i.getTime());return o=o&&"Invalid Date"==""+o?s:o,o&&(o.setHours(0),o.setMinutes(0),o.setSeconds(0),o.setMilliseconds(0)),this._daylightSavingAdjust(o)},_daylightSavingAdjust:function(e){return e?(e.setHours(e.getHours()>12?e.getHours()+2:0),e):null},_setDate:function(e,t,i){var s=!t,n=e.selectedMonth,a=e.selectedYear,o=this._restrictMinMax(e,this._determineDate(e,t,new Date));e.selectedDay=e.currentDay=o.getDate(),e.drawMonth=e.selectedMonth=e.currentMonth=o.getMonth(),e.drawYear=e.selectedYear=e.currentYear=o.getFullYear(),n===e.selectedMonth&&a===e.selectedYear||i||this._notifyChange(e),this._adjustInstDate(e),e.input&&e.input.val(s?"":this._formatDate(e))},_getDate:function(e){var t=!e.currentYear||e.input&&""===e.input.val()?null:this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return t},_attachHandlers:function(t){var i=this._get(t,"stepMonths"),s="#"+t.id.replace(/\\\\/g,"\\");t.dpDiv.find("[data-handler]").map(function(){var t={prev:function(){e.datepicker._adjustDate(s,-i,"M")},next:function(){e.datepicker._adjustDate(s,+i,"M")},hide:function(){e.datepicker._hideDatepicker()},today:function(){e.datepicker._gotoToday(s)},selectDay:function(){return e.datepicker._selectDay(s,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return e.datepicker._selectMonthYear(s,this,"M"),!1},selectYear:function(){return e.datepicker._selectMonthYear(s,this,"Y"),!1}};e(this).bind(this.getAttribute("data-event"),t[this.getAttribute("data-handler")])})},_generateHTML:function(e){var t,i,s,n,a,o,r,h,l,u,d,c,p,f,m,g,v,y,b,_,x,w,k,T,D,S,M,C,N,A,P,I,H,z,F,E,O,j,W,L=new Date,R=this._daylightSavingAdjust(new Date(L.getFullYear(),L.getMonth(),L.getDate())),Y=this._get(e,"isRTL"),B=this._get(e,"showButtonPanel"),J=this._get(e,"hideIfNoPrevNext"),q=this._get(e,"navigationAsDateFormat"),K=this._getNumberOfMonths(e),V=this._get(e,"showCurrentAtPos"),U=this._get(e,"stepMonths"),Q=1!==K[0]||1!==K[1],G=this._daylightSavingAdjust(e.currentDay?new Date(e.currentYear,e.currentMonth,e.currentDay):new Date(9999,9,9)),X=this._getMinMaxDate(e,"min"),$=this._getMinMaxDate(e,"max"),Z=e.drawMonth-V,et=e.drawYear;if(0>Z&&(Z+=12,et--),$)for(t=this._daylightSavingAdjust(new Date($.getFullYear(),$.getMonth()-K[0]*K[1]+1,$.getDate())),t=X&&X>t?X:t;this._daylightSavingAdjust(new Date(et,Z,1))>t;)Z--,0>Z&&(Z=11,et--);for(e.drawMonth=Z,e.drawYear=et,i=this._get(e,"prevText"),i=q?this.formatDate(i,this._daylightSavingAdjust(new Date(et,Z-U,1)),this._getFormatConfig(e)):i,s=this._canAdjustMonth(e,-1,et,Z)?"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click' title='"+i+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"e":"w")+"'>"+i+"</span></a>":J?"":"<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='"+i+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"e":"w")+"'>"+i+"</span></a>",n=this._get(e,"nextText"),n=q?this.formatDate(n,this._daylightSavingAdjust(new Date(et,Z+U,1)),this._getFormatConfig(e)):n,a=this._canAdjustMonth(e,1,et,Z)?"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click' title='"+n+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"w":"e")+"'>"+n+"</span></a>":J?"":"<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='"+n+"'><span class='ui-icon ui-icon-circle-triangle-"+(Y?"w":"e")+"'>"+n+"</span></a>",o=this._get(e,"currentText"),r=this._get(e,"gotoCurrent")&&e.currentDay?G:R,o=q?this.formatDate(o,r,this._getFormatConfig(e)):o,h=e.inline?"":"<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>"+this._get(e,"closeText")+"</button>",l=B?"<div class='ui-datepicker-buttonpane ui-widget-content'>"+(Y?h:"")+(this._isInRange(e,r)?"<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'>"+o+"</button>":"")+(Y?"":h)+"</div>":"",u=parseInt(this._get(e,"firstDay"),10),u=isNaN(u)?0:u,d=this._get(e,"showWeek"),c=this._get(e,"dayNames"),p=this._get(e,"dayNamesMin"),f=this._get(e,"monthNames"),m=this._get(e,"monthNamesShort"),g=this._get(e,"beforeShowDay"),v=this._get(e,"showOtherMonths"),y=this._get(e,"selectOtherMonths"),b=this._getDefaultDate(e),_="",w=0;K[0]>w;w++){for(k="",this.maxRows=4,T=0;K[1]>T;T++){if(D=this._daylightSavingAdjust(new Date(et,Z,e.selectedDay)),S=" ui-corner-all",M="",Q){if(M+="<div class='ui-datepicker-group",K[1]>1)switch(T){case 0:M+=" ui-datepicker-group-first",S=" ui-corner-"+(Y?"right":"left");break;case K[1]-1:M+=" ui-datepicker-group-last",S=" ui-corner-"+(Y?"left":"right");break;default:M+=" ui-datepicker-group-middle",S=""}M+="'>"}for(M+="<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix"+S+"'>"+(/all|left/.test(S)&&0===w?Y?a:s:"")+(/all|right/.test(S)&&0===w?Y?s:a:"")+this._generateMonthYearHeader(e,Z,et,X,$,w>0||T>0,f,m)+"</div><table class='ui-datepicker-calendar'><thead>"+"<tr>",C=d?"<th class='ui-datepicker-week-col'>"+this._get(e,"weekHeader")+"</th>":"",x=0;7>x;x++)N=(x+u)%7,C+="<th scope='col'"+((x+u+6)%7>=5?" class='ui-datepicker-week-end'":"")+">"+"<span title='"+c[N]+"'>"+p[N]+"</span></th>";for(M+=C+"</tr></thead><tbody>",A=this._getDaysInMonth(et,Z),et===e.selectedYear&&Z===e.selectedMonth&&(e.selectedDay=Math.min(e.selectedDay,A)),P=(this._getFirstDayOfMonth(et,Z)-u+7)%7,I=Math.ceil((P+A)/7),H=Q?this.maxRows>I?this.maxRows:I:I,this.maxRows=H,z=this._daylightSavingAdjust(new Date(et,Z,1-P)),F=0;H>F;F++){for(M+="<tr>",E=d?"<td class='ui-datepicker-week-col'>"+this._get(e,"calculateWeek")(z)+"</td>":"",x=0;7>x;x++)O=g?g.apply(e.input?e.input[0]:null,[z]):[!0,""],j=z.getMonth()!==Z,W=j&&!y||!O[0]||X&&X>z||$&&z>$,E+="<td class='"+((x+u+6)%7>=5?" ui-datepicker-week-end":"")+(j?" ui-datepicker-other-month":"")+(z.getTime()===D.getTime()&&Z===e.selectedMonth&&e._keyEvent||b.getTime()===z.getTime()&&b.getTime()===D.getTime()?" "+this._dayOverClass:"")+(W?" "+this._unselectableClass+" ui-state-disabled":"")+(j&&!v?"":" "+O[1]+(z.getTime()===G.getTime()?" "+this._currentClass:"")+(z.getTime()===R.getTime()?" ui-datepicker-today":""))+"'"+(j&&!v||!O[2]?"":" title='"+O[2].replace(/'/g,"&#39;")+"'")+(W?"":" data-handler='selectDay' data-event='click' data-month='"+z.getMonth()+"' data-year='"+z.getFullYear()+"'")+">"+(j&&!v?"&#xa0;":W?"<span class='ui-state-default'>"+z.getDate()+"</span>":"<a class='ui-state-default"+(z.getTime()===R.getTime()?" ui-state-highlight":"")+(z.getTime()===G.getTime()?" ui-state-active":"")+(j?" ui-priority-secondary":"")+"' href='#'>"+z.getDate()+"</a>")+"</td>",z.setDate(z.getDate()+1),z=this._daylightSavingAdjust(z);M+=E+"</tr>"}Z++,Z>11&&(Z=0,et++),M+="</tbody></table>"+(Q?"</div>"+(K[0]>0&&T===K[1]-1?"<div class='ui-datepicker-row-break'></div>":""):""),k+=M}_+=k}return _+=l,e._keyEvent=!1,_},_generateMonthYearHeader:function(e,t,i,s,n,a,o,r){var h,l,u,d,c,p,f,m,g=this._get(e,"changeMonth"),v=this._get(e,"changeYear"),y=this._get(e,"showMonthAfterYear"),b="<div class='ui-datepicker-title'>",_="";if(a||!g)_+="<span class='ui-datepicker-month'>"+o[t]+"</span>";else{for(h=s&&s.getFullYear()===i,l=n&&n.getFullYear()===i,_+="<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>",u=0;12>u;u++)(!h||u>=s.getMonth())&&(!l||n.getMonth()>=u)&&(_+="<option value='"+u+"'"+(u===t?" selected='selected'":"")+">"+r[u]+"</option>");_+="</select>"}if(y||(b+=_+(!a&&g&&v?"":"&#xa0;")),!e.yearshtml)if(e.yearshtml="",a||!v)b+="<span class='ui-datepicker-year'>"+i+"</span>";else{for(d=this._get(e,"yearRange").split(":"),c=(new Date).getFullYear(),p=function(e){var t=e.match(/c[+\-].*/)?i+parseInt(e.substring(1),10):e.match(/[+\-].*/)?c+parseInt(e,10):parseInt(e,10);return isNaN(t)?c:t},f=p(d[0]),m=Math.max(f,p(d[1]||"")),f=s?Math.max(f,s.getFullYear()):f,m=n?Math.min(m,n.getFullYear()):m,e.yearshtml+="<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>";m>=f;f++)e.yearshtml+="<option value='"+f+"'"+(f===i?" selected='selected'":"")+">"+f+"</option>";e.yearshtml+="</select>",b+=e.yearshtml,e.yearshtml=null}return b+=this._get(e,"yearSuffix"),y&&(b+=(!a&&g&&v?"":"&#xa0;")+_),b+="</div>"},_adjustInstDate:function(e,t,i){var s=e.drawYear+("Y"===i?t:0),n=e.drawMonth+("M"===i?t:0),a=Math.min(e.selectedDay,this._getDaysInMonth(s,n))+("D"===i?t:0),o=this._restrictMinMax(e,this._daylightSavingAdjust(new Date(s,n,a)));e.selectedDay=o.getDate(),e.drawMonth=e.selectedMonth=o.getMonth(),e.drawYear=e.selectedYear=o.getFullYear(),("M"===i||"Y"===i)&&this._notifyChange(e)},_restrictMinMax:function(e,t){var i=this._getMinMaxDate(e,"min"),s=this._getMinMaxDate(e,"max"),n=i&&i>t?i:t;return s&&n>s?s:n},_notifyChange:function(e){var t=this._get(e,"onChangeMonthYear");t&&t.apply(e.input?e.input[0]:null,[e.selectedYear,e.selectedMonth+1,e])},_getNumberOfMonths:function(e){var t=this._get(e,"numberOfMonths");return null==t?[1,1]:"number"==typeof t?[1,t]:t},_getMinMaxDate:function(e,t){return this._determineDate(e,this._get(e,t+"Date"),null)},_getDaysInMonth:function(e,t){return 32-this._daylightSavingAdjust(new Date(e,t,32)).getDate()},_getFirstDayOfMonth:function(e,t){return new Date(e,t,1).getDay()},_canAdjustMonth:function(e,t,i,s){var n=this._getNumberOfMonths(e),a=this._daylightSavingAdjust(new Date(i,s+(0>t?t:n[0]*n[1]),1));return 0>t&&a.setDate(this._getDaysInMonth(a.getFullYear(),a.getMonth())),this._isInRange(e,a)},_isInRange:function(e,t){var i,s,n=this._getMinMaxDate(e,"min"),a=this._getMinMaxDate(e,"max"),o=null,r=null,h=this._get(e,"yearRange");return h&&(i=h.split(":"),s=(new Date).getFullYear(),o=parseInt(i[0],10),r=parseInt(i[1],10),i[0].match(/[+\-].*/)&&(o+=s),i[1].match(/[+\-].*/)&&(r+=s)),(!n||t.getTime()>=n.getTime())&&(!a||t.getTime()<=a.getTime())&&(!o||t.getFullYear()>=o)&&(!r||r>=t.getFullYear())},_getFormatConfig:function(e){var t=this._get(e,"shortYearCutoff");return t="string"!=typeof t?t:(new Date).getFullYear()%100+parseInt(t,10),{shortYearCutoff:t,dayNamesShort:this._get(e,"dayNamesShort"),dayNames:this._get(e,"dayNames"),monthNamesShort:this._get(e,"monthNamesShort"),monthNames:this._get(e,"monthNames")}},_formatDate:function(e,t,i,s){t||(e.currentDay=e.selectedDay,e.currentMonth=e.selectedMonth,e.currentYear=e.selectedYear);var n=t?"object"==typeof t?t:this._daylightSavingAdjust(new Date(s,i,t)):this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return this.formatDate(this._get(e,"dateFormat"),n,this._getFormatConfig(e))}}),e.fn.datepicker=function(t){if(!this.length)return this;e.datepicker.initialized||(e(document).mousedown(e.datepicker._checkExternalClick),e.datepicker.initialized=!0),0===e("#"+e.datepicker._mainDivId).length&&e("body").append(e.datepicker.dpDiv);var i=Array.prototype.slice.call(arguments,1);return"string"!=typeof t||"isDisabled"!==t&&"getDate"!==t&&"widget"!==t?"option"===t&&2===arguments.length&&"string"==typeof arguments[1]?e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this[0]].concat(i)):this.each(function(){"string"==typeof t?e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this].concat(i)):e.datepicker._attachDatepicker(this,t)}):e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this[0]].concat(i))},e.datepicker=new n,e.datepicker.initialized=!1,e.datepicker.uuid=(new Date).getTime(),e.datepicker.version="1.11.4",e.datepicker,e.widget("ui.dialog",{version:"1.11.4",options:{appendTo:"body",autoOpen:!0,buttons:[],closeOnEscape:!0,closeText:"Close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(t){var i=e(this).css(t).offset().top;0>i&&e(this).css("top",t.top-i)}},resizable:!0,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},sizeRelatedOptions:{buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},resizableRelatedOptions:{maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height},this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.originalTitle=this.element.attr("title"),this.options.title=this.options.title||this.originalTitle,this._createWrapper(),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(this.uiDialog),this._createTitlebar(),this._createButtonPane(),this.options.draggable&&e.fn.draggable&&this._makeDraggable(),this.options.resizable&&e.fn.resizable&&this._makeResizable(),this._isOpen=!1,this._trackFocus()},_init:function(){this.options.autoOpen&&this.open()},_appendTo:function(){var t=this.options.appendTo;return t&&(t.jquery||t.nodeType)?e(t):this.document.find(t||"body").eq(0)},_destroy:function(){var e,t=this.originalPosition;this._untrackInstance(),this._destroyOverlay(),this.element.removeUniqueId().removeClass("ui-dialog-content ui-widget-content").css(this.originalCss).detach(),this.uiDialog.stop(!0,!0).remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),e=t.parent.children().eq(t.index),e.length&&e[0]!==this.element[0]?e.before(this.element):t.parent.append(this.element)},widget:function(){return this.uiDialog},disable:e.noop,enable:e.noop,close:function(t){var i,s=this;if(this._isOpen&&this._trigger("beforeClose",t)!==!1){if(this._isOpen=!1,this._focusedElement=null,this._destroyOverlay(),this._untrackInstance(),!this.opener.filter(":focusable").focus().length)try{i=this.document[0].activeElement,i&&"body"!==i.nodeName.toLowerCase()&&e(i).blur()}catch(n){}this._hide(this.uiDialog,this.options.hide,function(){s._trigger("close",t)})}},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(t,i){var s=!1,n=this.uiDialog.siblings(".ui-front:visible").map(function(){return+e(this).css("z-index")}).get(),a=Math.max.apply(null,n);return a>=+this.uiDialog.css("z-index")&&(this.uiDialog.css("z-index",a+1),s=!0),s&&!i&&this._trigger("focus",t),s},open:function(){var t=this;
+return this._isOpen?(this._moveToTop()&&this._focusTabbable(),void 0):(this._isOpen=!0,this.opener=e(this.document[0].activeElement),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this.overlay&&this.overlay.css("z-index",this.uiDialog.css("z-index")-1),this._show(this.uiDialog,this.options.show,function(){t._focusTabbable(),t._trigger("focus")}),this._makeFocusTarget(),this._trigger("open"),void 0)},_focusTabbable:function(){var e=this._focusedElement;e||(e=this.element.find("[autofocus]")),e.length||(e=this.element.find(":tabbable")),e.length||(e=this.uiDialogButtonPane.find(":tabbable")),e.length||(e=this.uiDialogTitlebarClose.filter(":tabbable")),e.length||(e=this.uiDialog),e.eq(0).focus()},_keepFocus:function(t){function i(){var t=this.document[0].activeElement,i=this.uiDialog[0]===t||e.contains(this.uiDialog[0],t);i||this._focusTabbable()}t.preventDefault(),i.call(this),this._delay(i)},_createWrapper:function(){this.uiDialog=e("<div>").addClass("ui-dialog ui-widget ui-widget-content ui-corner-all ui-front "+this.options.dialogClass).hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo()),this._on(this.uiDialog,{keydown:function(t){if(this.options.closeOnEscape&&!t.isDefaultPrevented()&&t.keyCode&&t.keyCode===e.ui.keyCode.ESCAPE)return t.preventDefault(),this.close(t),void 0;if(t.keyCode===e.ui.keyCode.TAB&&!t.isDefaultPrevented()){var i=this.uiDialog.find(":tabbable"),s=i.filter(":first"),n=i.filter(":last");t.target!==n[0]&&t.target!==this.uiDialog[0]||t.shiftKey?t.target!==s[0]&&t.target!==this.uiDialog[0]||!t.shiftKey||(this._delay(function(){n.focus()}),t.preventDefault()):(this._delay(function(){s.focus()}),t.preventDefault())}},mousedown:function(e){this._moveToTop(e)&&this._focusTabbable()}}),this.element.find("[aria-describedby]").length||this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})},_createTitlebar:function(){var t;this.uiDialogTitlebar=e("<div>").addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(this.uiDialog),this._on(this.uiDialogTitlebar,{mousedown:function(t){e(t.target).closest(".ui-dialog-titlebar-close")||this.uiDialog.focus()}}),this.uiDialogTitlebarClose=e("<button type='button'></button>").button({label:this.options.closeText,icons:{primary:"ui-icon-closethick"},text:!1}).addClass("ui-dialog-titlebar-close").appendTo(this.uiDialogTitlebar),this._on(this.uiDialogTitlebarClose,{click:function(e){e.preventDefault(),this.close(e)}}),t=e("<span>").uniqueId().addClass("ui-dialog-title").prependTo(this.uiDialogTitlebar),this._title(t),this.uiDialog.attr({"aria-labelledby":t.attr("id")})},_title:function(e){this.options.title||e.html("&#160;"),e.text(this.options.title)},_createButtonPane:function(){this.uiDialogButtonPane=e("<div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),this.uiButtonSet=e("<div>").addClass("ui-dialog-buttonset").appendTo(this.uiDialogButtonPane),this._createButtons()},_createButtons:function(){var t=this,i=this.options.buttons;return this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),e.isEmptyObject(i)||e.isArray(i)&&!i.length?(this.uiDialog.removeClass("ui-dialog-buttons"),void 0):(e.each(i,function(i,s){var n,a;s=e.isFunction(s)?{click:s,text:i}:s,s=e.extend({type:"button"},s),n=s.click,s.click=function(){n.apply(t.element[0],arguments)},a={icons:s.icons,text:s.showText},delete s.icons,delete s.showText,e("<button></button>",s).button(a).appendTo(t.uiButtonSet)}),this.uiDialog.addClass("ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog),void 0)},_makeDraggable:function(){function t(e){return{position:e.position,offset:e.offset}}var i=this,s=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(s,n){e(this).addClass("ui-dialog-dragging"),i._blockFrames(),i._trigger("dragStart",s,t(n))},drag:function(e,s){i._trigger("drag",e,t(s))},stop:function(n,a){var o=a.offset.left-i.document.scrollLeft(),r=a.offset.top-i.document.scrollTop();s.position={my:"left top",at:"left"+(o>=0?"+":"")+o+" "+"top"+(r>=0?"+":"")+r,of:i.window},e(this).removeClass("ui-dialog-dragging"),i._unblockFrames(),i._trigger("dragStop",n,t(a))}})},_makeResizable:function(){function t(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}var i=this,s=this.options,n=s.resizable,a=this.uiDialog.css("position"),o="string"==typeof n?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:s.maxWidth,maxHeight:s.maxHeight,minWidth:s.minWidth,minHeight:this._minHeight(),handles:o,start:function(s,n){e(this).addClass("ui-dialog-resizing"),i._blockFrames(),i._trigger("resizeStart",s,t(n))},resize:function(e,s){i._trigger("resize",e,t(s))},stop:function(n,a){var o=i.uiDialog.offset(),r=o.left-i.document.scrollLeft(),h=o.top-i.document.scrollTop();s.height=i.uiDialog.height(),s.width=i.uiDialog.width(),s.position={my:"left top",at:"left"+(r>=0?"+":"")+r+" "+"top"+(h>=0?"+":"")+h,of:i.window},e(this).removeClass("ui-dialog-resizing"),i._unblockFrames(),i._trigger("resizeStop",n,t(a))}}).css("position",a)},_trackFocus:function(){this._on(this.widget(),{focusin:function(t){this._makeFocusTarget(),this._focusedElement=e(t.target)}})},_makeFocusTarget:function(){this._untrackInstance(),this._trackingInstances().unshift(this)},_untrackInstance:function(){var t=this._trackingInstances(),i=e.inArray(this,t);-1!==i&&t.splice(i,1)},_trackingInstances:function(){var e=this.document.data("ui-dialog-instances");return e||(e=[],this.document.data("ui-dialog-instances",e)),e},_minHeight:function(){var e=this.options;return"auto"===e.height?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(){var e=this.uiDialog.is(":visible");e||this.uiDialog.show(),this.uiDialog.position(this.options.position),e||this.uiDialog.hide()},_setOptions:function(t){var i=this,s=!1,n={};e.each(t,function(e,t){i._setOption(e,t),e in i.sizeRelatedOptions&&(s=!0),e in i.resizableRelatedOptions&&(n[e]=t)}),s&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",n)},_setOption:function(e,t){var i,s,n=this.uiDialog;"dialogClass"===e&&n.removeClass(this.options.dialogClass).addClass(t),"disabled"!==e&&(this._super(e,t),"appendTo"===e&&this.uiDialog.appendTo(this._appendTo()),"buttons"===e&&this._createButtons(),"closeText"===e&&this.uiDialogTitlebarClose.button({label:""+t}),"draggable"===e&&(i=n.is(":data(ui-draggable)"),i&&!t&&n.draggable("destroy"),!i&&t&&this._makeDraggable()),"position"===e&&this._position(),"resizable"===e&&(s=n.is(":data(ui-resizable)"),s&&!t&&n.resizable("destroy"),s&&"string"==typeof t&&n.resizable("option","handles",t),s||t===!1||this._makeResizable()),"title"===e&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")))},_size:function(){var e,t,i,s=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),s.minWidth>s.width&&(s.width=s.minWidth),e=this.uiDialog.css({height:"auto",width:s.width}).outerHeight(),t=Math.max(0,s.minHeight-e),i="number"==typeof s.maxHeight?Math.max(0,s.maxHeight-e):"none","auto"===s.height?this.element.css({minHeight:t,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,s.height-e)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var t=e(this);return e("<div>").css({position:"absolute",width:t.outerWidth(),height:t.outerHeight()}).appendTo(t.parent()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(t){return e(t.target).closest(".ui-dialog").length?!0:!!e(t.target).closest(".ui-datepicker").length},_createOverlay:function(){if(this.options.modal){var t=!0;this._delay(function(){t=!1}),this.document.data("ui-dialog-overlays")||this._on(this.document,{focusin:function(e){t||this._allowInteraction(e)||(e.preventDefault(),this._trackingInstances()[0]._focusTabbable())}}),this.overlay=e("<div>").addClass("ui-widget-overlay ui-front").appendTo(this._appendTo()),this._on(this.overlay,{mousedown:"_keepFocus"}),this.document.data("ui-dialog-overlays",(this.document.data("ui-dialog-overlays")||0)+1)}},_destroyOverlay:function(){if(this.options.modal&&this.overlay){var e=this.document.data("ui-dialog-overlays")-1;e?this.document.data("ui-dialog-overlays",e):this.document.unbind("focusin").removeData("ui-dialog-overlays"),this.overlay.remove(),this.overlay=null}}}),e.widget("ui.progressbar",{version:"1.11.4",options:{max:100,value:0,change:null,complete:null},min:0,_create:function(){this.oldValue=this.options.value=this._constrainedValue(),this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min}),this.valueDiv=e("<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>").appendTo(this.element),this._refreshValue()},_destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove()},value:function(e){return void 0===e?this.options.value:(this.options.value=this._constrainedValue(e),this._refreshValue(),void 0)},_constrainedValue:function(e){return void 0===e&&(e=this.options.value),this.indeterminate=e===!1,"number"!=typeof e&&(e=0),this.indeterminate?!1:Math.min(this.options.max,Math.max(this.min,e))},_setOptions:function(e){var t=e.value;delete e.value,this._super(e),this.options.value=this._constrainedValue(t),this._refreshValue()},_setOption:function(e,t){"max"===e&&(t=Math.max(this.min,t)),"disabled"===e&&this.element.toggleClass("ui-state-disabled",!!t).attr("aria-disabled",t),this._super(e,t)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var t=this.options.value,i=this._percentage();this.valueDiv.toggle(this.indeterminate||t>this.min).toggleClass("ui-corner-right",t===this.options.max).width(i.toFixed(0)+"%"),this.element.toggleClass("ui-progressbar-indeterminate",this.indeterminate),this.indeterminate?(this.element.removeAttr("aria-valuenow"),this.overlayDiv||(this.overlayDiv=e("<div class='ui-progressbar-overlay'></div>").appendTo(this.valueDiv))):(this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":t}),this.overlayDiv&&(this.overlayDiv.remove(),this.overlayDiv=null)),this.oldValue!==t&&(this.oldValue=t,this._trigger("change")),t===this.options.max&&this._trigger("complete")}}),e.widget("ui.selectmenu",{version:"1.11.4",defaultElement:"<select>",options:{appendTo:null,disabled:null,icons:{button:"ui-icon-triangle-1-s"},position:{my:"left top",at:"left bottom",collision:"none"},width:null,change:null,close:null,focus:null,open:null,select:null},_create:function(){var e=this.element.uniqueId().attr("id");this.ids={element:e,button:e+"-button",menu:e+"-menu"},this._drawButton(),this._drawMenu(),this.options.disabled&&this.disable()},_drawButton:function(){var t=this;this.label=e("label[for='"+this.ids.element+"']").attr("for",this.ids.button),this._on(this.label,{click:function(e){this.button.focus(),e.preventDefault()}}),this.element.hide(),this.button=e("<span>",{"class":"ui-selectmenu-button ui-widget ui-state-default ui-corner-all",tabindex:this.options.disabled?-1:0,id:this.ids.button,role:"combobox","aria-expanded":"false","aria-autocomplete":"list","aria-owns":this.ids.menu,"aria-haspopup":"true"}).insertAfter(this.element),e("<span>",{"class":"ui-icon "+this.options.icons.button}).prependTo(this.button),this.buttonText=e("<span>",{"class":"ui-selectmenu-text"}).appendTo(this.button),this._setText(this.buttonText,this.element.find("option:selected").text()),this._resizeButton(),this._on(this.button,this._buttonEvents),this.button.one("focusin",function(){t.menuItems||t._refreshMenu()}),this._hoverable(this.button),this._focusable(this.button)},_drawMenu:function(){var t=this;this.menu=e("<ul>",{"aria-hidden":"true","aria-labelledby":this.ids.button,id:this.ids.menu}),this.menuWrap=e("<div>",{"class":"ui-selectmenu-menu ui-front"}).append(this.menu).appendTo(this._appendTo()),this.menuInstance=this.menu.menu({role:"listbox",select:function(e,i){e.preventDefault(),t._setSelection(),t._select(i.item.data("ui-selectmenu-item"),e)},focus:function(e,i){var s=i.item.data("ui-selectmenu-item");null!=t.focusIndex&&s.index!==t.focusIndex&&(t._trigger("focus",e,{item:s}),t.isOpen||t._select(s,e)),t.focusIndex=s.index,t.button.attr("aria-activedescendant",t.menuItems.eq(s.index).attr("id"))}}).menu("instance"),this.menu.addClass("ui-corner-bottom").removeClass("ui-corner-all"),this.menuInstance._off(this.menu,"mouseleave"),this.menuInstance._closeOnDocumentClick=function(){return!1},this.menuInstance._isDivider=function(){return!1}},refresh:function(){this._refreshMenu(),this._setText(this.buttonText,this._getSelectedItem().text()),this.options.width||this._resizeButton()},_refreshMenu:function(){this.menu.empty();var e,t=this.element.find("option");t.length&&(this._parseOptions(t),this._renderMenu(this.menu,this.items),this.menuInstance.refresh(),this.menuItems=this.menu.find("li").not(".ui-selectmenu-optgroup"),e=this._getSelectedItem(),this.menuInstance.focus(null,e),this._setAria(e.data("ui-selectmenu-item")),this._setOption("disabled",this.element.prop("disabled")))},open:function(e){this.options.disabled||(this.menuItems?(this.menu.find(".ui-state-focus").removeClass("ui-state-focus"),this.menuInstance.focus(null,this._getSelectedItem())):this._refreshMenu(),this.isOpen=!0,this._toggleAttr(),this._resizeMenu(),this._position(),this._on(this.document,this._documentClick),this._trigger("open",e))},_position:function(){this.menuWrap.position(e.extend({of:this.button},this.options.position))},close:function(e){this.isOpen&&(this.isOpen=!1,this._toggleAttr(),this.range=null,this._off(this.document),this._trigger("close",e))},widget:function(){return this.button},menuWidget:function(){return this.menu},_renderMenu:function(t,i){var s=this,n="";e.each(i,function(i,a){a.optgroup!==n&&(e("<li>",{"class":"ui-selectmenu-optgroup ui-menu-divider"+(a.element.parent("optgroup").prop("disabled")?" ui-state-disabled":""),text:a.optgroup}).appendTo(t),n=a.optgroup),s._renderItemData(t,a)})},_renderItemData:function(e,t){return this._renderItem(e,t).data("ui-selectmenu-item",t)},_renderItem:function(t,i){var s=e("<li>");return i.disabled&&s.addClass("ui-state-disabled"),this._setText(s,i.label),s.appendTo(t)},_setText:function(e,t){t?e.text(t):e.html("&#160;")},_move:function(e,t){var i,s,n=".ui-menu-item";this.isOpen?i=this.menuItems.eq(this.focusIndex):(i=this.menuItems.eq(this.element[0].selectedIndex),n+=":not(.ui-state-disabled)"),s="first"===e||"last"===e?i["first"===e?"prevAll":"nextAll"](n).eq(-1):i[e+"All"](n).eq(0),s.length&&this.menuInstance.focus(t,s)},_getSelectedItem:function(){return this.menuItems.eq(this.element[0].selectedIndex)},_toggle:function(e){this[this.isOpen?"close":"open"](e)},_setSelection:function(){var e;this.range&&(window.getSelection?(e=window.getSelection(),e.removeAllRanges(),e.addRange(this.range)):this.range.select(),this.button.focus())},_documentClick:{mousedown:function(t){this.isOpen&&(e(t.target).closest(".ui-selectmenu-menu, #"+this.ids.button).length||this.close(t))}},_buttonEvents:{mousedown:function(){var e;window.getSelection?(e=window.getSelection(),e.rangeCount&&(this.range=e.getRangeAt(0))):this.range=document.selection.createRange()},click:function(e){this._setSelection(),this._toggle(e)},keydown:function(t){var i=!0;switch(t.keyCode){case e.ui.keyCode.TAB:case e.ui.keyCode.ESCAPE:this.close(t),i=!1;break;case e.ui.keyCode.ENTER:this.isOpen&&this._selectFocusedItem(t);break;case e.ui.keyCode.UP:t.altKey?this._toggle(t):this._move("prev",t);break;case e.ui.keyCode.DOWN:t.altKey?this._toggle(t):this._move("next",t);break;case e.ui.keyCode.SPACE:this.isOpen?this._selectFocusedItem(t):this._toggle(t);break;case e.ui.keyCode.LEFT:this._move("prev",t);break;case e.ui.keyCode.RIGHT:this._move("next",t);break;case e.ui.keyCode.HOME:case e.ui.keyCode.PAGE_UP:this._move("first",t);break;case e.ui.keyCode.END:case e.ui.keyCode.PAGE_DOWN:this._move("last",t);break;default:this.menu.trigger(t),i=!1}i&&t.preventDefault()}},_selectFocusedItem:function(e){var t=this.menuItems.eq(this.focusIndex);t.hasClass("ui-state-disabled")||this._select(t.data("ui-selectmenu-item"),e)},_select:function(e,t){var i=this.element[0].selectedIndex;this.element[0].selectedIndex=e.index,this._setText(this.buttonText,e.label),this._setAria(e),this._trigger("select",t,{item:e}),e.index!==i&&this._trigger("change",t,{item:e}),this.close(t)},_setAria:function(e){var t=this.menuItems.eq(e.index).attr("id");this.button.attr({"aria-labelledby":t,"aria-activedescendant":t}),this.menu.attr("aria-activedescendant",t)},_setOption:function(e,t){"icons"===e&&this.button.find("span.ui-icon").removeClass(this.options.icons.button).addClass(t.button),this._super(e,t),"appendTo"===e&&this.menuWrap.appendTo(this._appendTo()),"disabled"===e&&(this.menuInstance.option("disabled",t),this.button.toggleClass("ui-state-disabled",t).attr("aria-disabled",t),this.element.prop("disabled",t),t?(this.button.attr("tabindex",-1),this.close()):this.button.attr("tabindex",0)),"width"===e&&this._resizeButton()},_appendTo:function(){var t=this.options.appendTo;return t&&(t=t.jquery||t.nodeType?e(t):this.document.find(t).eq(0)),t&&t[0]||(t=this.element.closest(".ui-front")),t.length||(t=this.document[0].body),t},_toggleAttr:function(){this.button.toggleClass("ui-corner-top",this.isOpen).toggleClass("ui-corner-all",!this.isOpen).attr("aria-expanded",this.isOpen),this.menuWrap.toggleClass("ui-selectmenu-open",this.isOpen),this.menu.attr("aria-hidden",!this.isOpen)},_resizeButton:function(){var e=this.options.width;e||(e=this.element.show().outerWidth(),this.element.hide()),this.button.outerWidth(e)},_resizeMenu:function(){this.menu.outerWidth(Math.max(this.button.outerWidth(),this.menu.width("").outerWidth()+1))},_getCreateOptions:function(){return{disabled:this.element.prop("disabled")}},_parseOptions:function(t){var i=[];t.each(function(t,s){var n=e(s),a=n.parent("optgroup");i.push({element:n,index:t,value:n.val(),label:n.text(),optgroup:a.attr("label")||"",disabled:a.prop("disabled")||n.prop("disabled")})}),this.items=i},_destroy:function(){this.menuWrap.remove(),this.button.remove(),this.element.show(),this.element.removeUniqueId(),this.label.attr("for",this.ids.element)}}),e.widget("ui.slider",e.ui.mouse,{version:"1.11.4",widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null,change:null,slide:null,start:null,stop:null},numPages:5,_create:function(){this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this._calculateNewMax(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"),this._refresh(),this._setOption("disabled",this.options.disabled),this._animateOff=!1},_refresh:function(){this._createRange(),this._createHandles(),this._setupEvents(),this._refreshValue()},_createHandles:function(){var t,i,s=this.options,n=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),a="<span class='ui-slider-handle ui-state-default ui-corner-all' tabindex='0'></span>",o=[];for(i=s.values&&s.values.length||1,n.length>i&&(n.slice(i).remove(),n=n.slice(0,i)),t=n.length;i>t;t++)o.push(a);this.handles=n.add(e(o.join("")).appendTo(this.element)),this.handle=this.handles.eq(0),this.handles.each(function(t){e(this).data("ui-slider-handle-index",t)})},_createRange:function(){var t=this.options,i="";t.range?(t.range===!0&&(t.values?t.values.length&&2!==t.values.length?t.values=[t.values[0],t.values[0]]:e.isArray(t.values)&&(t.values=t.values.slice(0)):t.values=[this._valueMin(),this._valueMin()]),this.range&&this.range.length?this.range.removeClass("ui-slider-range-min ui-slider-range-max").css({left:"",bottom:""}):(this.range=e("<div></div>").appendTo(this.element),i="ui-slider-range ui-widget-header ui-corner-all"),this.range.addClass(i+("min"===t.range||"max"===t.range?" ui-slider-range-"+t.range:""))):(this.range&&this.range.remove(),this.range=null)},_setupEvents:function(){this._off(this.handles),this._on(this.handles,this._handleEvents),this._hoverable(this.handles),this._focusable(this.handles)},_destroy:function(){this.handles.remove(),this.range&&this.range.remove(),this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-widget ui-widget-content ui-corner-all"),this._mouseDestroy()},_mouseCapture:function(t){var i,s,n,a,o,r,h,l,u=this,d=this.options;return d.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),i={x:t.pageX,y:t.pageY},s=this._normValueFromMouse(i),n=this._valueMax()-this._valueMin()+1,this.handles.each(function(t){var i=Math.abs(s-u.values(t));(n>i||n===i&&(t===u._lastChangedValue||u.values(t)===d.min))&&(n=i,a=e(this),o=t)}),r=this._start(t,o),r===!1?!1:(this._mouseSliding=!0,this._handleIndex=o,a.addClass("ui-state-active").focus(),h=a.offset(),l=!e(t.target).parents().addBack().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:t.pageX-h.left-a.width()/2,top:t.pageY-h.top-a.height()/2-(parseInt(a.css("borderTopWidth"),10)||0)-(parseInt(a.css("borderBottomWidth"),10)||0)+(parseInt(a.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(t,o,s),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(e){var t={x:e.pageX,y:e.pageY},i=this._normValueFromMouse(t);return this._slide(e,this._handleIndex,i),!1},_mouseStop:function(e){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(e,this._handleIndex),this._change(e,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation="vertical"===this.options.orientation?"vertical":"horizontal"},_normValueFromMouse:function(e){var t,i,s,n,a;return"horizontal"===this.orientation?(t=this.elementSize.width,i=e.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(t=this.elementSize.height,i=e.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),s=i/t,s>1&&(s=1),0>s&&(s=0),"vertical"===this.orientation&&(s=1-s),n=this._valueMax()-this._valueMin(),a=this._valueMin()+s*n,this._trimAlignValue(a)},_start:function(e,t){var i={handle:this.handles[t],value:this.value()};return this.options.values&&this.options.values.length&&(i.value=this.values(t),i.values=this.values()),this._trigger("start",e,i)},_slide:function(e,t,i){var s,n,a;this.options.values&&this.options.values.length?(s=this.values(t?0:1),2===this.options.values.length&&this.options.range===!0&&(0===t&&i>s||1===t&&s>i)&&(i=s),i!==this.values(t)&&(n=this.values(),n[t]=i,a=this._trigger("slide",e,{handle:this.handles[t],value:i,values:n}),s=this.values(t?0:1),a!==!1&&this.values(t,i))):i!==this.value()&&(a=this._trigger("slide",e,{handle:this.handles[t],value:i}),a!==!1&&this.value(i))},_stop:function(e,t){var i={handle:this.handles[t],value:this.value()};this.options.values&&this.options.values.length&&(i.value=this.values(t),i.values=this.values()),this._trigger("stop",e,i)},_change:function(e,t){if(!this._keySliding&&!this._mouseSliding){var i={handle:this.handles[t],value:this.value()};this.options.values&&this.options.values.length&&(i.value=this.values(t),i.values=this.values()),this._lastChangedValue=t,this._trigger("change",e,i)}},value:function(e){return arguments.length?(this.options.value=this._trimAlignValue(e),this._refreshValue(),this._change(null,0),void 0):this._value()},values:function(t,i){var s,n,a;if(arguments.length>1)return this.options.values[t]=this._trimAlignValue(i),this._refreshValue(),this._change(null,t),void 0;if(!arguments.length)return this._values();if(!e.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(t):this.value();for(s=this.options.values,n=arguments[0],a=0;s.length>a;a+=1)s[a]=this._trimAlignValue(n[a]),this._change(null,a);this._refreshValue()},_setOption:function(t,i){var s,n=0;switch("range"===t&&this.options.range===!0&&("min"===i?(this.options.value=this._values(0),this.options.values=null):"max"===i&&(this.options.value=this._values(this.options.values.length-1),this.options.values=null)),e.isArray(this.options.values)&&(n=this.options.values.length),"disabled"===t&&this.element.toggleClass("ui-state-disabled",!!i),this._super(t,i),t){case"orientation":this._detectOrientation(),this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation),this._refreshValue(),this.handles.css("horizontal"===i?"bottom":"left","");break;case"value":this._animateOff=!0,this._refreshValue(),this._change(null,0),this._animateOff=!1;break;case"values":for(this._animateOff=!0,this._refreshValue(),s=0;n>s;s+=1)this._change(null,s);this._animateOff=!1;break;case"step":case"min":case"max":this._animateOff=!0,this._calculateNewMax(),this._refreshValue(),this._animateOff=!1;break;case"range":this._animateOff=!0,this._refresh(),this._animateOff=!1}},_value:function(){var e=this.options.value;return e=this._trimAlignValue(e)},_values:function(e){var t,i,s;if(arguments.length)return t=this.options.values[e],t=this._trimAlignValue(t);if(this.options.values&&this.options.values.length){for(i=this.options.values.slice(),s=0;i.length>s;s+=1)i[s]=this._trimAlignValue(i[s]);return i}return[]},_trimAlignValue:function(e){if(this._valueMin()>=e)return this._valueMin();if(e>=this._valueMax())return this._valueMax();var t=this.options.step>0?this.options.step:1,i=(e-this._valueMin())%t,s=e-i;return 2*Math.abs(i)>=t&&(s+=i>0?t:-t),parseFloat(s.toFixed(5))},_calculateNewMax:function(){var e=this.options.max,t=this._valueMin(),i=this.options.step,s=Math.floor(+(e-t).toFixed(this._precision())/i)*i;e=s+t,this.max=parseFloat(e.toFixed(this._precision()))},_precision:function(){var e=this._precisionOf(this.options.step);return null!==this.options.min&&(e=Math.max(e,this._precisionOf(this.options.min))),e},_precisionOf:function(e){var t=""+e,i=t.indexOf(".");return-1===i?0:t.length-i-1},_valueMin:function(){return this.options.min},_valueMax:function(){return this.max},_refreshValue:function(){var t,i,s,n,a,o=this.options.range,r=this.options,h=this,l=this._animateOff?!1:r.animate,u={};this.options.values&&this.options.values.length?this.handles.each(function(s){i=100*((h.values(s)-h._valueMin())/(h._valueMax()-h._valueMin())),u["horizontal"===h.orientation?"left":"bottom"]=i+"%",e(this).stop(1,1)[l?"animate":"css"](u,r.animate),h.options.range===!0&&("horizontal"===h.orientation?(0===s&&h.range.stop(1,1)[l?"animate":"css"]({left:i+"%"},r.animate),1===s&&h.range[l?"animate":"css"]({width:i-t+"%"},{queue:!1,duration:r.animate})):(0===s&&h.range.stop(1,1)[l?"animate":"css"]({bottom:i+"%"},r.animate),1===s&&h.range[l?"animate":"css"]({height:i-t+"%"},{queue:!1,duration:r.animate}))),t=i}):(s=this.value(),n=this._valueMin(),a=this._valueMax(),i=a!==n?100*((s-n)/(a-n)):0,u["horizontal"===this.orientation?"left":"bottom"]=i+"%",this.handle.stop(1,1)[l?"animate":"css"](u,r.animate),"min"===o&&"horizontal"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({width:i+"%"},r.animate),"max"===o&&"horizontal"===this.orientation&&this.range[l?"animate":"css"]({width:100-i+"%"},{queue:!1,duration:r.animate}),"min"===o&&"vertical"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({height:i+"%"},r.animate),"max"===o&&"vertical"===this.orientation&&this.range[l?"animate":"css"]({height:100-i+"%"},{queue:!1,duration:r.animate}))},_handleEvents:{keydown:function(t){var i,s,n,a,o=e(t.target).data("ui-slider-handle-index");switch(t.keyCode){case e.ui.keyCode.HOME:case e.ui.keyCode.END:case e.ui.keyCode.PAGE_UP:case e.ui.keyCode.PAGE_DOWN:case e.ui.keyCode.UP:case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:case e.ui.keyCode.LEFT:if(t.preventDefault(),!this._keySliding&&(this._keySliding=!0,e(t.target).addClass("ui-state-active"),i=this._start(t,o),i===!1))return}switch(a=this.options.step,s=n=this.options.values&&this.options.values.length?this.values(o):this.value(),t.keyCode){case e.ui.keyCode.HOME:n=this._valueMin();break;case e.ui.keyCode.END:n=this._valueMax();break;case e.ui.keyCode.PAGE_UP:n=this._trimAlignValue(s+(this._valueMax()-this._valueMin())/this.numPages);break;case e.ui.keyCode.PAGE_DOWN:n=this._trimAlignValue(s-(this._valueMax()-this._valueMin())/this.numPages);break;case e.ui.keyCode.UP:case e.ui.keyCode.RIGHT:if(s===this._valueMax())return;n=this._trimAlignValue(s+a);break;case e.ui.keyCode.DOWN:case e.ui.keyCode.LEFT:if(s===this._valueMin())return;n=this._trimAlignValue(s-a)}this._slide(t,o,n)},keyup:function(t){var i=e(t.target).data("ui-slider-handle-index");this._keySliding&&(this._keySliding=!1,this._stop(t,i),this._change(t,i),e(t.target).removeClass("ui-state-active"))}}}),e.widget("ui.spinner",{version:"1.11.4",defaultElement:"<input>",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var t={},i=this.element;return e.each(["min","max","step"],function(e,s){var n=i.attr(s);void 0!==n&&n.length&&(t[s]=n)}),t},_events:{keydown:function(e){this._start(e)&&this._keydown(e)&&e.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,void 0):(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",e),void 0)},mousewheel:function(e,t){if(t){if(!this.spinning&&!this._start(e))return!1;this._spin((t>0?1:-1)*this.options.step,e),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(e)},100),e.preventDefault()}},"mousedown .ui-spinner-button":function(t){function i(){var e=this.element[0]===this.document[0].activeElement;e||(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s}))}var s;s=this.element[0]===this.document[0].activeElement?this.previous:this.element.val(),t.preventDefault(),i.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,i.call(this)}),this._start(t)!==!1&&this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(t){return e(t.currentTarget).hasClass("ui-state-active")?this._start(t)===!1?!1:(this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t),void 0):void 0},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var e=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton"),this.buttons=e.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all"),this.buttons.height()>Math.ceil(.5*e.height())&&e.height()>0&&e.height(e.height()),this.options.disabled&&this.disable()
+},_keydown:function(t){var i=this.options,s=e.ui.keyCode;switch(t.keyCode){case s.UP:return this._repeat(null,1,t),!0;case s.DOWN:return this._repeat(null,-1,t),!0;case s.PAGE_UP:return this._repeat(null,i.page,t),!0;case s.PAGE_DOWN:return this._repeat(null,-i.page,t),!0}return!1},_uiSpinnerHtml:function(){return"<span class='ui-spinner ui-widget ui-widget-content ui-corner-all'></span>"},_buttonHtml:function(){return"<a class='ui-spinner-button ui-spinner-up ui-corner-tr'><span class='ui-icon "+this.options.icons.up+"'>&#9650;</span>"+"</a>"+"<a class='ui-spinner-button ui-spinner-down ui-corner-br'>"+"<span class='ui-icon "+this.options.icons.down+"'>&#9660;</span>"+"</a>"},_start:function(e){return this.spinning||this._trigger("start",e)!==!1?(this.counter||(this.counter=1),this.spinning=!0,!0):!1},_repeat:function(e,t,i){e=e||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,t,i)},e),this._spin(t*this.options.step,i)},_spin:function(e,t){var i=this.value()||0;this.counter||(this.counter=1),i=this._adjustValue(i+e*this._increment(this.counter)),this.spinning&&this._trigger("spin",t,{value:i})===!1||(this._value(i),this.counter++)},_increment:function(t){var i=this.options.incremental;return i?e.isFunction(i)?i(t):Math.floor(t*t*t/5e4-t*t/500+17*t/200+1):1},_precision:function(){var e=this._precisionOf(this.options.step);return null!==this.options.min&&(e=Math.max(e,this._precisionOf(this.options.min))),e},_precisionOf:function(e){var t=""+e,i=t.indexOf(".");return-1===i?0:t.length-i-1},_adjustValue:function(e){var t,i,s=this.options;return t=null!==s.min?s.min:0,i=e-t,i=Math.round(i/s.step)*s.step,e=t+i,e=parseFloat(e.toFixed(this._precision())),null!==s.max&&e>s.max?s.max:null!==s.min&&s.min>e?s.min:e},_stop:function(e){this.spinning&&(clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",e))},_setOption:function(e,t){if("culture"===e||"numberFormat"===e){var i=this._parse(this.element.val());return this.options[e]=t,this.element.val(this._format(i)),void 0}("max"===e||"min"===e||"step"===e)&&"string"==typeof t&&(t=this._parse(t)),"icons"===e&&(this.buttons.first().find(".ui-icon").removeClass(this.options.icons.up).addClass(t.up),this.buttons.last().find(".ui-icon").removeClass(this.options.icons.down).addClass(t.down)),this._super(e,t),"disabled"===e&&(this.widget().toggleClass("ui-state-disabled",!!t),this.element.prop("disabled",!!t),this.buttons.button(t?"disable":"enable"))},_setOptions:h(function(e){this._super(e)}),_parse:function(e){return"string"==typeof e&&""!==e&&(e=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(e,10,this.options.culture):+e),""===e||isNaN(e)?null:e},_format:function(e){return""===e?"":window.Globalize&&this.options.numberFormat?Globalize.format(e,this.options.numberFormat,this.options.culture):e},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},isValid:function(){var e=this.value();return null===e?!1:e===this._adjustValue(e)},_value:function(e,t){var i;""!==e&&(i=this._parse(e),null!==i&&(t||(i=this._adjustValue(i)),e=this._format(i))),this.element.val(e),this._refresh()},_destroy:function(){this.element.removeClass("ui-spinner-input").prop("disabled",!1).removeAttr("autocomplete").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:h(function(e){this._stepUp(e)}),_stepUp:function(e){this._start()&&(this._spin((e||1)*this.options.step),this._stop())},stepDown:h(function(e){this._stepDown(e)}),_stepDown:function(e){this._start()&&(this._spin((e||1)*-this.options.step),this._stop())},pageUp:h(function(e){this._stepUp((e||1)*this.options.page)}),pageDown:h(function(e){this._stepDown((e||1)*this.options.page)}),value:function(e){return arguments.length?(h(this._value).call(this,e),void 0):this._parse(this.element.val())},widget:function(){return this.uiSpinner}}),e.widget("ui.tabs",{version:"1.11.4",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_isLocal:function(){var e=/#.*$/;return function(t){var i,s;t=t.cloneNode(!1),i=t.href.replace(e,""),s=location.href.replace(e,"");try{i=decodeURIComponent(i)}catch(n){}try{s=decodeURIComponent(s)}catch(n){}return t.hash.length>1&&i===s}}(),_create:function(){var t=this,i=this.options;this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",i.collapsible),this._processTabs(),i.active=this._initialActive(),e.isArray(i.disabled)&&(i.disabled=e.unique(i.disabled.concat(e.map(this.tabs.filter(".ui-state-disabled"),function(e){return t.tabs.index(e)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(i.active):e(),this._refresh(),this.active.length&&this.load(i.active)},_initialActive:function(){var t=this.options.active,i=this.options.collapsible,s=location.hash.substring(1);return null===t&&(s&&this.tabs.each(function(i,n){return e(n).attr("aria-controls")===s?(t=i,!1):void 0}),null===t&&(t=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===t||-1===t)&&(t=this.tabs.length?0:!1)),t!==!1&&(t=this.tabs.index(this.tabs.eq(t)),-1===t&&(t=i?!1:0)),!i&&t===!1&&this.anchors.length&&(t=0),t},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):e()}},_tabKeydown:function(t){var i=e(this.document[0].activeElement).closest("li"),s=this.tabs.index(i),n=!0;if(!this._handlePageNav(t)){switch(t.keyCode){case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:s++;break;case e.ui.keyCode.UP:case e.ui.keyCode.LEFT:n=!1,s--;break;case e.ui.keyCode.END:s=this.anchors.length-1;break;case e.ui.keyCode.HOME:s=0;break;case e.ui.keyCode.SPACE:return t.preventDefault(),clearTimeout(this.activating),this._activate(s),void 0;case e.ui.keyCode.ENTER:return t.preventDefault(),clearTimeout(this.activating),this._activate(s===this.options.active?!1:s),void 0;default:return}t.preventDefault(),clearTimeout(this.activating),s=this._focusNextTab(s,n),t.ctrlKey||t.metaKey||(i.attr("aria-selected","false"),this.tabs.eq(s).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",s)},this.delay))}},_panelKeydown:function(t){this._handlePageNav(t)||t.ctrlKey&&t.keyCode===e.ui.keyCode.UP&&(t.preventDefault(),this.active.focus())},_handlePageNav:function(t){return t.altKey&&t.keyCode===e.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):t.altKey&&t.keyCode===e.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):void 0},_findNextTab:function(t,i){function s(){return t>n&&(t=0),0>t&&(t=n),t}for(var n=this.tabs.length-1;-1!==e.inArray(s(),this.options.disabled);)t=i?t+1:t-1;return t},_focusNextTab:function(e,t){return e=this._findNextTab(e,t),this.tabs.eq(e).focus(),e},_setOption:function(e,t){return"active"===e?(this._activate(t),void 0):"disabled"===e?(this._setupDisabled(t),void 0):(this._super(e,t),"collapsible"===e&&(this.element.toggleClass("ui-tabs-collapsible",t),t||this.options.active!==!1||this._activate(0)),"event"===e&&this._setupEvents(t),"heightStyle"===e&&this._setupHeightStyle(t),void 0)},_sanitizeSelector:function(e){return e?e.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var t=this.options,i=this.tablist.children(":has(a[href])");t.disabled=e.map(i.filter(".ui-state-disabled"),function(e){return i.index(e)}),this._processTabs(),t.active!==!1&&this.anchors.length?this.active.length&&!e.contains(this.tablist[0],this.active[0])?this.tabs.length===t.disabled.length?(t.active=!1,this.active=e()):this._activate(this._findNextTab(Math.max(0,t.active-1),!1)):t.active=this.tabs.index(this.active):(t.active=!1,this.active=e()),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var t=this,i=this.tabs,s=this.anchors,n=this.panels;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist").delegate("> li","mousedown"+this.eventNamespace,function(t){e(this).is(".ui-state-disabled")&&t.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){e(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return e("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=e(),this.anchors.each(function(i,s){var n,a,o,r=e(s).uniqueId().attr("id"),h=e(s).closest("li"),l=h.attr("aria-controls");t._isLocal(s)?(n=s.hash,o=n.substring(1),a=t.element.find(t._sanitizeSelector(n))):(o=h.attr("aria-controls")||e({}).uniqueId()[0].id,n="#"+o,a=t.element.find(n),a.length||(a=t._createPanel(o),a.insertAfter(t.panels[i-1]||t.tablist)),a.attr("aria-live","polite")),a.length&&(t.panels=t.panels.add(a)),l&&h.data("ui-tabs-aria-controls",l),h.attr({"aria-controls":o,"aria-labelledby":r}),a.attr("aria-labelledby",r)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel"),i&&(this._off(i.not(this.tabs)),this._off(s.not(this.anchors)),this._off(n.not(this.panels)))},_getList:function(){return this.tablist||this.element.find("ol,ul").eq(0)},_createPanel:function(t){return e("<div>").attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(t){e.isArray(t)&&(t.length?t.length===this.anchors.length&&(t=!0):t=!1);for(var i,s=0;i=this.tabs[s];s++)t===!0||-1!==e.inArray(s,t)?e(i).addClass("ui-state-disabled").attr("aria-disabled","true"):e(i).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=t},_setupEvents:function(t){var i={};t&&e.each(t.split(" "),function(e,t){i[t]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(!0,this.anchors,{click:function(e){e.preventDefault()}}),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(t){var i,s=this.element.parent();"fill"===t?(i=s.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var t=e(this),s=t.css("position");"absolute"!==s&&"fixed"!==s&&(i-=t.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=e(this).outerHeight(!0)}),this.panels.each(function(){e(this).height(Math.max(0,i-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):"auto"===t&&(i=0,this.panels.each(function(){i=Math.max(i,e(this).height("").height())}).height(i))},_eventHandler:function(t){var i=this.options,s=this.active,n=e(t.currentTarget),a=n.closest("li"),o=a[0]===s[0],r=o&&i.collapsible,h=r?e():this._getPanelForTab(a),l=s.length?this._getPanelForTab(s):e(),u={oldTab:s,oldPanel:l,newTab:r?e():a,newPanel:h};t.preventDefault(),a.hasClass("ui-state-disabled")||a.hasClass("ui-tabs-loading")||this.running||o&&!i.collapsible||this._trigger("beforeActivate",t,u)===!1||(i.active=r?!1:this.tabs.index(a),this.active=o?e():a,this.xhr&&this.xhr.abort(),l.length||h.length||e.error("jQuery UI Tabs: Mismatching fragment identifier."),h.length&&this.load(this.tabs.index(a),t),this._toggle(t,u))},_toggle:function(t,i){function s(){a.running=!1,a._trigger("activate",t,i)}function n(){i.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),o.length&&a.options.show?a._show(o,a.options.show,s):(o.show(),s())}var a=this,o=i.newPanel,r=i.oldPanel;this.running=!0,r.length&&this.options.hide?this._hide(r,this.options.hide,function(){i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),n()}):(i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),r.hide(),n()),r.attr("aria-hidden","true"),i.oldTab.attr({"aria-selected":"false","aria-expanded":"false"}),o.length&&r.length?i.oldTab.attr("tabIndex",-1):o.length&&this.tabs.filter(function(){return 0===e(this).attr("tabIndex")}).attr("tabIndex",-1),o.attr("aria-hidden","false"),i.newTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_activate:function(t){var i,s=this._findActive(t);s[0]!==this.active[0]&&(s.length||(s=this.active),i=s.find(".ui-tabs-anchor")[0],this._eventHandler({target:i,currentTarget:i,preventDefault:e.noop}))},_findActive:function(t){return t===!1?e():this.tabs.eq(t)},_getIndex:function(e){return"string"==typeof e&&(e=this.anchors.index(this.anchors.filter("[href$='"+e+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeUniqueId(),this.tablist.unbind(this.eventNamespace),this.tabs.add(this.panels).each(function(){e.data(this,"ui-tabs-destroy")?e(this).remove():e(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var t=e(this),i=t.data("ui-tabs-aria-controls");i?t.attr("aria-controls",i).removeData("ui-tabs-aria-controls"):t.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(t){var i=this.options.disabled;i!==!1&&(void 0===t?i=!1:(t=this._getIndex(t),i=e.isArray(i)?e.map(i,function(e){return e!==t?e:null}):e.map(this.tabs,function(e,i){return i!==t?i:null})),this._setupDisabled(i))},disable:function(t){var i=this.options.disabled;if(i!==!0){if(void 0===t)i=!0;else{if(t=this._getIndex(t),-1!==e.inArray(t,i))return;i=e.isArray(i)?e.merge([t],i).sort():[t]}this._setupDisabled(i)}},load:function(t,i){t=this._getIndex(t);var s=this,n=this.tabs.eq(t),a=n.find(".ui-tabs-anchor"),o=this._getPanelForTab(n),r={tab:n,panel:o},h=function(e,t){"abort"===t&&s.panels.stop(!1,!0),n.removeClass("ui-tabs-loading"),o.removeAttr("aria-busy"),e===s.xhr&&delete s.xhr};this._isLocal(a[0])||(this.xhr=e.ajax(this._ajaxSettings(a,i,r)),this.xhr&&"canceled"!==this.xhr.statusText&&(n.addClass("ui-tabs-loading"),o.attr("aria-busy","true"),this.xhr.done(function(e,t,n){setTimeout(function(){o.html(e),s._trigger("load",i,r),h(n,t)},1)}).fail(function(e,t){setTimeout(function(){h(e,t)},1)})))},_ajaxSettings:function(t,i,s){var n=this;return{url:t.attr("href"),beforeSend:function(t,a){return n._trigger("beforeLoad",i,e.extend({jqXHR:t,ajaxSettings:a},s))}}},_getPanelForTab:function(t){var i=e(t).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+i))}}),e.widget("ui.tooltip",{version:"1.11.4",options:{content:function(){var t=e(this).attr("title")||"";return e("<a>").text(t).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_addDescribedBy:function(t,i){var s=(t.attr("aria-describedby")||"").split(/\s+/);s.push(i),t.data("ui-tooltip-id",i).attr("aria-describedby",e.trim(s.join(" ")))},_removeDescribedBy:function(t){var i=t.data("ui-tooltip-id"),s=(t.attr("aria-describedby")||"").split(/\s+/),n=e.inArray(i,s);-1!==n&&s.splice(n,1),t.removeData("ui-tooltip-id"),s=e.trim(s.join(" ")),s?t.attr("aria-describedby",s):t.removeAttr("aria-describedby")},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable(),this.liveRegion=e("<div>").attr({role:"log","aria-live":"assertive","aria-relevant":"additions"}).addClass("ui-helper-hidden-accessible").appendTo(this.document[0].body)},_setOption:function(t,i){var s=this;return"disabled"===t?(this[i?"_disable":"_enable"](),this.options[t]=i,void 0):(this._super(t,i),"content"===t&&e.each(this.tooltips,function(e,t){s._updateContent(t.element)}),void 0)},_disable:function(){var t=this;e.each(this.tooltips,function(i,s){var n=e.Event("blur");n.target=n.currentTarget=s.element[0],t.close(n,!0)}),this.element.find(this.options.items).addBack().each(function(){var t=e(this);t.is("[title]")&&t.data("ui-tooltip-title",t.attr("title")).removeAttr("title")})},_enable:function(){this.element.find(this.options.items).addBack().each(function(){var t=e(this);t.data("ui-tooltip-title")&&t.attr("title",t.data("ui-tooltip-title"))})},open:function(t){var i=this,s=e(t?t.target:this.element).closest(this.options.items);s.length&&!s.data("ui-tooltip-id")&&(s.attr("title")&&s.data("ui-tooltip-title",s.attr("title")),s.data("ui-tooltip-open",!0),t&&"mouseover"===t.type&&s.parents().each(function(){var t,s=e(this);s.data("ui-tooltip-open")&&(t=e.Event("blur"),t.target=t.currentTarget=this,i.close(t,!0)),s.attr("title")&&(s.uniqueId(),i.parents[this.id]={element:this,title:s.attr("title")},s.attr("title",""))}),this._registerCloseHandlers(t,s),this._updateContent(s,t))},_updateContent:function(e,t){var i,s=this.options.content,n=this,a=t?t.type:null;return"string"==typeof s?this._open(t,e,s):(i=s.call(e[0],function(i){n._delay(function(){e.data("ui-tooltip-open")&&(t&&(t.type=a),this._open(t,e,i))})}),i&&this._open(t,e,i),void 0)},_open:function(t,i,s){function n(e){l.of=e,o.is(":hidden")||o.position(l)}var a,o,r,h,l=e.extend({},this.options.position);if(s){if(a=this._find(i))return a.tooltip.find(".ui-tooltip-content").html(s),void 0;i.is("[title]")&&(t&&"mouseover"===t.type?i.attr("title",""):i.removeAttr("title")),a=this._tooltip(i),o=a.tooltip,this._addDescribedBy(i,o.attr("id")),o.find(".ui-tooltip-content").html(s),this.liveRegion.children().hide(),s.clone?(h=s.clone(),h.removeAttr("id").find("[id]").removeAttr("id")):h=s,e("<div>").html(h).appendTo(this.liveRegion),this.options.track&&t&&/^mouse/.test(t.type)?(this._on(this.document,{mousemove:n}),n(t)):o.position(e.extend({of:i},this.options.position)),o.hide(),this._show(o,this.options.show),this.options.show&&this.options.show.delay&&(r=this.delayedShow=setInterval(function(){o.is(":visible")&&(n(l.of),clearInterval(r))},e.fx.interval)),this._trigger("open",t,{tooltip:o})}},_registerCloseHandlers:function(t,i){var s={keyup:function(t){if(t.keyCode===e.ui.keyCode.ESCAPE){var s=e.Event(t);s.currentTarget=i[0],this.close(s,!0)}}};i[0]!==this.element[0]&&(s.remove=function(){this._removeTooltip(this._find(i).tooltip)}),t&&"mouseover"!==t.type||(s.mouseleave="close"),t&&"focusin"!==t.type||(s.focusout="close"),this._on(!0,i,s)},close:function(t){var i,s=this,n=e(t?t.currentTarget:this.element),a=this._find(n);return a?(i=a.tooltip,a.closing||(clearInterval(this.delayedShow),n.data("ui-tooltip-title")&&!n.attr("title")&&n.attr("title",n.data("ui-tooltip-title")),this._removeDescribedBy(n),a.hiding=!0,i.stop(!0),this._hide(i,this.options.hide,function(){s._removeTooltip(e(this))}),n.removeData("ui-tooltip-open"),this._off(n,"mouseleave focusout keyup"),n[0]!==this.element[0]&&this._off(n,"remove"),this._off(this.document,"mousemove"),t&&"mouseleave"===t.type&&e.each(this.parents,function(t,i){e(i.element).attr("title",i.title),delete s.parents[t]}),a.closing=!0,this._trigger("close",t,{tooltip:i}),a.hiding||(a.closing=!1)),void 0):(n.removeData("ui-tooltip-open"),void 0)},_tooltip:function(t){var i=e("<div>").attr("role","tooltip").addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||"")),s=i.uniqueId().attr("id");return e("<div>").addClass("ui-tooltip-content").appendTo(i),i.appendTo(this.document[0].body),this.tooltips[s]={element:t,tooltip:i}},_find:function(e){var t=e.data("ui-tooltip-id");return t?this.tooltips[t]:null},_removeTooltip:function(e){e.remove(),delete this.tooltips[e.attr("id")]},_destroy:function(){var t=this;e.each(this.tooltips,function(i,s){var n=e.Event("blur"),a=s.element;n.target=n.currentTarget=a[0],t.close(n,!0),e("#"+i).remove(),a.data("ui-tooltip-title")&&(a.attr("title")||a.attr("title",a.data("ui-tooltip-title")),a.removeData("ui-tooltip-title"))}),this.liveRegion.remove()}});var y="ui-effects-",b=e;e.effects={effect:{}},function(e,t){function i(e,t,i){var s=d[t.type]||{};return null==e?i||!t.def?null:t.def:(e=s.floor?~~e:parseFloat(e),isNaN(e)?t.def:s.mod?(e+s.mod)%s.mod:0>e?0:e>s.max?s.max:e)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(e,a){var o,r=a.re.exec(i),h=r&&a.parse(r),l=a.space||"rgba";return h?(o=s[l](h),s[u[l].cache]=o[u[l].cache],n=s._rgba=o._rgba,!1):t}),n.length?("0,0,0,0"===n.join()&&e.extend(n,a.transparent),s):a[i]}function n(e,t,i){return i=(i+1)%1,1>6*i?e+6*(t-e)*i:1>2*i?t:2>3*i?e+6*(t-e)*(2/3-i):e}var a,o="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(e){return[e[1],e[2],e[3],e[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(e){return[2.55*e[1],2.55*e[2],2.55*e[3],e[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(e){return[parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(e){return[parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(e){return[e[1],e[2]/100,e[3]/100,e[4]]}}],l=e.Color=function(t,i,s,n){return new e.Color.fn.parse(t,i,s,n)},u={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},d={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},c=l.support={},p=e("<p>")[0],f=e.each;p.style.cssText="background-color:rgba(1,1,1,.5)",c.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(u,function(e,t){t.cache="_"+e,t.props.alpha={idx:3,type:"percent",def:1}}),l.fn=e.extend(l.prototype,{parse:function(n,o,r,h){if(n===t)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=e(n).css(o),o=t);var d=this,c=e.type(n),p=this._rgba=[];return o!==t&&(n=[n,o,r,h],c="array"),"string"===c?this.parse(s(n)||a._default):"array"===c?(f(u.rgba.props,function(e,t){p[t.idx]=i(n[t.idx],t)}),this):"object"===c?(n instanceof l?f(u,function(e,t){n[t.cache]&&(d[t.cache]=n[t.cache].slice())}):f(u,function(t,s){var a=s.cache;f(s.props,function(e,t){if(!d[a]&&s.to){if("alpha"===e||null==n[e])return;d[a]=s.to(d._rgba)}d[a][t.idx]=i(n[e],t,!0)}),d[a]&&0>e.inArray(null,d[a].slice(0,3))&&(d[a][3]=1,s.from&&(d._rgba=s.from(d[a])))}),this):t},is:function(e){var i=l(e),s=!0,n=this;return f(u,function(e,a){var o,r=i[a.cache];return r&&(o=n[a.cache]||a.to&&a.to(n._rgba)||[],f(a.props,function(e,i){return null!=r[i.idx]?s=r[i.idx]===o[i.idx]:t})),s}),s},_space:function(){var e=[],t=this;return f(u,function(i,s){t[s.cache]&&e.push(i)}),e.pop()},transition:function(e,t){var s=l(e),n=s._space(),a=u[n],o=0===this.alpha()?l("transparent"):this,r=o[a.cache]||a.to(o._rgba),h=r.slice();return s=s[a.cache],f(a.props,function(e,n){var a=n.idx,o=r[a],l=s[a],u=d[n.type]||{};null!==l&&(null===o?h[a]=l:(u.mod&&(l-o>u.mod/2?o+=u.mod:o-l>u.mod/2&&(o-=u.mod)),h[a]=i((l-o)*t+o,n)))}),this[n](h)},blend:function(t){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(t)._rgba;return l(e.map(i,function(e,t){return(1-s)*n[t]+s*e}))},toRgbaString:function(){var t="rgba(",i=e.map(this._rgba,function(e,t){return null==e?t>2?1:0:e});return 1===i[3]&&(i.pop(),t="rgb("),t+i.join()+")"},toHslaString:function(){var t="hsla(",i=e.map(this.hsla(),function(e,t){return null==e&&(e=t>2?1:0),t&&3>t&&(e=Math.round(100*e)+"%"),e});return 1===i[3]&&(i.pop(),t="hsl("),t+i.join()+")"},toHexString:function(t){var i=this._rgba.slice(),s=i.pop();return t&&i.push(~~(255*s)),"#"+e.map(i,function(e){return e=(e||0).toString(16),1===e.length?"0"+e:e}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,u.hsla.to=function(e){if(null==e[0]||null==e[1]||null==e[2])return[null,null,null,e[3]];var t,i,s=e[0]/255,n=e[1]/255,a=e[2]/255,o=e[3],r=Math.max(s,n,a),h=Math.min(s,n,a),l=r-h,u=r+h,d=.5*u;return t=h===r?0:s===r?60*(n-a)/l+360:n===r?60*(a-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=d?l/u:l/(2-u),[Math.round(t)%360,i,d,null==o?1:o]},u.hsla.from=function(e){if(null==e[0]||null==e[1]||null==e[2])return[null,null,null,e[3]];var t=e[0]/360,i=e[1],s=e[2],a=e[3],o=.5>=s?s*(1+i):s+i-s*i,r=2*s-o;return[Math.round(255*n(r,o,t+1/3)),Math.round(255*n(r,o,t)),Math.round(255*n(r,o,t-1/3)),a]},f(u,function(s,n){var a=n.props,o=n.cache,h=n.to,u=n.from;l.fn[s]=function(s){if(h&&!this[o]&&(this[o]=h(this._rgba)),s===t)return this[o].slice();var n,r=e.type(s),d="array"===r||"object"===r?s:arguments,c=this[o].slice();return f(a,function(e,t){var s=d["object"===r?e:t.idx];null==s&&(s=c[t.idx]),c[t.idx]=i(s,t)}),u?(n=l(u(c)),n[o]=c,n):l(c)},f(a,function(t,i){l.fn[t]||(l.fn[t]=function(n){var a,o=e.type(n),h="alpha"===t?this._hsla?"hsla":"rgba":s,l=this[h](),u=l[i.idx];return"undefined"===o?u:("function"===o&&(n=n.call(this,u),o=e.type(n)),null==n&&i.empty?this:("string"===o&&(a=r.exec(n),a&&(n=u+parseFloat(a[2])*("+"===a[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(t){var i=t.split(" ");f(i,function(t,i){e.cssHooks[i]={set:function(t,n){var a,o,r="";if("transparent"!==n&&("string"!==e.type(n)||(a=s(n)))){if(n=l(a||n),!c.rgba&&1!==n._rgba[3]){for(o="backgroundColor"===i?t.parentNode:t;(""===r||"transparent"===r)&&o&&o.style;)try{r=e.css(o,"backgroundColor"),o=o.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{t.style[i]=n}catch(h){}}},e.fx.step[i]=function(t){t.colorInit||(t.start=l(t.elem,i),t.end=l(t.end),t.colorInit=!0),e.cssHooks[i].set(t.elem,t.start.transition(t.end,t.pos))}})},l.hook(o),e.cssHooks.borderColor={expand:function(e){var t={};return f(["Top","Right","Bottom","Left"],function(i,s){t["border"+s+"Color"]=e}),t}},a=e.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(b),function(){function t(t){var i,s,n=t.ownerDocument.defaultView?t.ownerDocument.defaultView.getComputedStyle(t,null):t.currentStyle,a={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(a[e.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(a[i]=n[i]);return a}function i(t,i){var s,a,o={};for(s in i)a=i[s],t[s]!==a&&(n[s]||(e.fx.step[s]||!isNaN(parseFloat(a)))&&(o[s]=a));return o}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};e.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(t,i){e.fx.step[i]=function(e){("none"!==e.end&&!e.setAttr||1===e.pos&&!e.setAttr)&&(b.style(e.elem,i,e.end),e.setAttr=!0)}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e.effects.animateClass=function(n,a,o,r){var h=e.speed(a,o,r);return this.queue(function(){var a,o=e(this),r=o.attr("class")||"",l=h.children?o.find("*").addBack():o;l=l.map(function(){var i=e(this);return{el:i,start:t(this)}}),a=function(){e.each(s,function(e,t){n[t]&&o[t+"Class"](n[t])})},a(),l=l.map(function(){return this.end=t(this.el[0]),this.diff=i(this.start,this.end),this}),o.attr("class",r),l=l.map(function(){var t=this,i=e.Deferred(),s=e.extend({},h,{queue:!1,complete:function(){i.resolve(t)}});return this.el.animate(this.diff,s),i.promise()}),e.when.apply(e,l.get()).done(function(){a(),e.each(arguments,function(){var t=this.el;e.each(this.diff,function(e){t.css(e,"")})}),h.complete.call(o[0])})})},e.fn.extend({addClass:function(t){return function(i,s,n,a){return s?e.effects.animateClass.call(this,{add:i},s,n,a):t.apply(this,arguments)}}(e.fn.addClass),removeClass:function(t){return function(i,s,n,a){return arguments.length>1?e.effects.animateClass.call(this,{remove:i},s,n,a):t.apply(this,arguments)}}(e.fn.removeClass),toggleClass:function(t){return function(i,s,n,a,o){return"boolean"==typeof s||void 0===s?n?e.effects.animateClass.call(this,s?{add:i}:{remove:i},n,a,o):t.apply(this,arguments):e.effects.animateClass.call(this,{toggle:i},s,n,a)}}(e.fn.toggleClass),switchClass:function(t,i,s,n,a){return e.effects.animateClass.call(this,{add:i,remove:t},s,n,a)}})}(),function(){function t(t,i,s,n){return e.isPlainObject(t)&&(i=t,t=t.effect),t={effect:t},null==i&&(i={}),e.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||e.fx.speeds[i])&&(n=s,s=i,i={}),e.isFunction(s)&&(n=s,s=null),i&&e.extend(t,i),s=s||i.duration,t.duration=e.fx.off?0:"number"==typeof s?s:s in e.fx.speeds?e.fx.speeds[s]:e.fx.speeds._default,t.complete=n||i.complete,t}function i(t){return!t||"number"==typeof t||e.fx.speeds[t]?!0:"string"!=typeof t||e.effects.effect[t]?e.isFunction(t)?!0:"object"!=typeof t||t.effect?!1:!0:!0}e.extend(e.effects,{version:"1.11.4",save:function(e,t){for(var i=0;t.length>i;i++)null!==t[i]&&e.data(y+t[i],e[0].style[t[i]])},restore:function(e,t){var i,s;for(s=0;t.length>s;s++)null!==t[s]&&(i=e.data(y+t[s]),void 0===i&&(i=""),e.css(t[s],i))},setMode:function(e,t){return"toggle"===t&&(t=e.is(":hidden")?"show":"hide"),t},getBaseline:function(e,t){var i,s;switch(e[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=e[0]/t.height}switch(e[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=e[1]/t.width}return{x:s,y:i}},createWrapper:function(t){if(t.parent().is(".ui-effects-wrapper"))return t.parent();var i={width:t.outerWidth(!0),height:t.outerHeight(!0),"float":t.css("float")},s=e("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:t.width(),height:t.height()},a=document.activeElement;try{a.id}catch(o){a=document.body}return t.wrap(s),(t[0]===a||e.contains(t[0],a))&&e(a).focus(),s=t.parent(),"static"===t.css("position")?(s.css({position:"relative"}),t.css({position:"relative"})):(e.extend(i,{position:t.css("position"),zIndex:t.css("z-index")}),e.each(["top","left","bottom","right"],function(e,s){i[s]=t.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),t.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),t.css(n),s.css(i).show()},removeWrapper:function(t){var i=document.activeElement;
+return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),(t[0]===i||e.contains(t[0],i))&&e(i).focus()),t},setTransition:function(t,i,s,n){return n=n||{},e.each(i,function(e,i){var a=t.cssUnit(i);a[0]>0&&(n[i]=a[0]*s+a[1])}),n}}),e.fn.extend({effect:function(){function i(t){function i(){e.isFunction(a)&&a.call(n[0]),e.isFunction(t)&&t()}var n=e(this),a=s.complete,r=s.mode;(n.is(":hidden")?"hide"===r:"show"===r)?(n[r](),i()):o.call(n[0],s,i)}var s=t.apply(this,arguments),n=s.mode,a=s.queue,o=e.effects.effect[s.effect];return e.fx.off||!o?n?this[n](s.duration,s.complete):this.each(function(){s.complete&&s.complete.call(this)}):a===!1?this.each(i):this.queue(a||"fx",i)},show:function(e){return function(s){if(i(s))return e.apply(this,arguments);var n=t.apply(this,arguments);return n.mode="show",this.effect.call(this,n)}}(e.fn.show),hide:function(e){return function(s){if(i(s))return e.apply(this,arguments);var n=t.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(e.fn.hide),toggle:function(e){return function(s){if(i(s)||"boolean"==typeof s)return e.apply(this,arguments);var n=t.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(e.fn.toggle),cssUnit:function(t){var i=this.css(t),s=[];return e.each(["em","px","%","pt"],function(e,t){i.indexOf(t)>0&&(s=[parseFloat(i),t])}),s}})}(),function(){var t={};e.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,i){t[i]=function(t){return Math.pow(t,e+2)}}),e.extend(t,{Sine:function(e){return 1-Math.cos(e*Math.PI/2)},Circ:function(e){return 1-Math.sqrt(1-e*e)},Elastic:function(e){return 0===e||1===e?e:-Math.pow(2,8*(e-1))*Math.sin((80*(e-1)-7.5)*Math.PI/15)},Back:function(e){return e*e*(3*e-2)},Bounce:function(e){for(var t,i=4;((t=Math.pow(2,--i))-1)/11>e;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*t-2)/22-e,2)}}),e.each(t,function(t,i){e.easing["easeIn"+t]=i,e.easing["easeOut"+t]=function(e){return 1-i(1-e)},e.easing["easeInOut"+t]=function(e){return.5>e?i(2*e)/2:1-i(-2*e+2)/2}})}(),e.effects,e.effects.effect.blind=function(t,i){var s,n,a,o=e(this),r=/up|down|vertical/,h=/up|left|vertical|horizontal/,l=["position","top","bottom","left","right","height","width"],u=e.effects.setMode(o,t.mode||"hide"),d=t.direction||"up",c=r.test(d),p=c?"height":"width",f=c?"top":"left",m=h.test(d),g={},v="show"===u;o.parent().is(".ui-effects-wrapper")?e.effects.save(o.parent(),l):e.effects.save(o,l),o.show(),s=e.effects.createWrapper(o).css({overflow:"hidden"}),n=s[p](),a=parseFloat(s.css(f))||0,g[p]=v?n:0,m||(o.css(c?"bottom":"right",0).css(c?"top":"left","auto").css({position:"absolute"}),g[f]=v?a:n+a),v&&(s.css(p,0),m||s.css(f,a+n)),s.animate(g,{duration:t.duration,easing:t.easing,queue:!1,complete:function(){"hide"===u&&o.hide(),e.effects.restore(o,l),e.effects.removeWrapper(o),i()}})},e.effects.effect.bounce=function(t,i){var s,n,a,o=e(this),r=["position","top","bottom","left","right","height","width"],h=e.effects.setMode(o,t.mode||"effect"),l="hide"===h,u="show"===h,d=t.direction||"up",c=t.distance,p=t.times||5,f=2*p+(u||l?1:0),m=t.duration/f,g=t.easing,v="up"===d||"down"===d?"top":"left",y="up"===d||"left"===d,b=o.queue(),_=b.length;for((u||l)&&r.push("opacity"),e.effects.save(o,r),o.show(),e.effects.createWrapper(o),c||(c=o["top"===v?"outerHeight":"outerWidth"]()/3),u&&(a={opacity:1},a[v]=0,o.css("opacity",0).css(v,y?2*-c:2*c).animate(a,m,g)),l&&(c/=Math.pow(2,p-1)),a={},a[v]=0,s=0;p>s;s++)n={},n[v]=(y?"-=":"+=")+c,o.animate(n,m,g).animate(a,m,g),c=l?2*c:c/2;l&&(n={opacity:0},n[v]=(y?"-=":"+=")+c,o.animate(n,m,g)),o.queue(function(){l&&o.hide(),e.effects.restore(o,r),e.effects.removeWrapper(o),i()}),_>1&&b.splice.apply(b,[1,0].concat(b.splice(_,f+1))),o.dequeue()},e.effects.effect.clip=function(t,i){var s,n,a,o=e(this),r=["position","top","bottom","left","right","height","width"],h=e.effects.setMode(o,t.mode||"hide"),l="show"===h,u=t.direction||"vertical",d="vertical"===u,c=d?"height":"width",p=d?"top":"left",f={};e.effects.save(o,r),o.show(),s=e.effects.createWrapper(o).css({overflow:"hidden"}),n="IMG"===o[0].tagName?s:o,a=n[c](),l&&(n.css(c,0),n.css(p,a/2)),f[c]=l?a:0,f[p]=l?0:a/2,n.animate(f,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){l||o.hide(),e.effects.restore(o,r),e.effects.removeWrapper(o),i()}})},e.effects.effect.drop=function(t,i){var s,n=e(this),a=["position","top","bottom","left","right","opacity","height","width"],o=e.effects.setMode(n,t.mode||"hide"),r="show"===o,h=t.direction||"left",l="up"===h||"down"===h?"top":"left",u="up"===h||"left"===h?"pos":"neg",d={opacity:r?1:0};e.effects.save(n,a),n.show(),e.effects.createWrapper(n),s=t.distance||n["top"===l?"outerHeight":"outerWidth"](!0)/2,r&&n.css("opacity",0).css(l,"pos"===u?-s:s),d[l]=(r?"pos"===u?"+=":"-=":"pos"===u?"-=":"+=")+s,n.animate(d,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){"hide"===o&&n.hide(),e.effects.restore(n,a),e.effects.removeWrapper(n),i()}})},e.effects.effect.explode=function(t,i){function s(){b.push(this),b.length===d*c&&n()}function n(){p.css({visibility:"visible"}),e(b).remove(),m||p.hide(),i()}var a,o,r,h,l,u,d=t.pieces?Math.round(Math.sqrt(t.pieces)):3,c=d,p=e(this),f=e.effects.setMode(p,t.mode||"hide"),m="show"===f,g=p.show().css("visibility","hidden").offset(),v=Math.ceil(p.outerWidth()/c),y=Math.ceil(p.outerHeight()/d),b=[];for(a=0;d>a;a++)for(h=g.top+a*y,u=a-(d-1)/2,o=0;c>o;o++)r=g.left+o*v,l=o-(c-1)/2,p.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-o*v,top:-a*y}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:v,height:y,left:r+(m?l*v:0),top:h+(m?u*y:0),opacity:m?0:1}).animate({left:r+(m?0:l*v),top:h+(m?0:u*y),opacity:m?1:0},t.duration||500,t.easing,s)},e.effects.effect.fade=function(t,i){var s=e(this),n=e.effects.setMode(s,t.mode||"toggle");s.animate({opacity:n},{queue:!1,duration:t.duration,easing:t.easing,complete:i})},e.effects.effect.fold=function(t,i){var s,n,a=e(this),o=["position","top","bottom","left","right","height","width"],r=e.effects.setMode(a,t.mode||"hide"),h="show"===r,l="hide"===r,u=t.size||15,d=/([0-9]+)%/.exec(u),c=!!t.horizFirst,p=h!==c,f=p?["width","height"]:["height","width"],m=t.duration/2,g={},v={};e.effects.save(a,o),a.show(),s=e.effects.createWrapper(a).css({overflow:"hidden"}),n=p?[s.width(),s.height()]:[s.height(),s.width()],d&&(u=parseInt(d[1],10)/100*n[l?0:1]),h&&s.css(c?{height:0,width:u}:{height:u,width:0}),g[f[0]]=h?n[0]:u,v[f[1]]=h?n[1]:0,s.animate(g,m,t.easing).animate(v,m,t.easing,function(){l&&a.hide(),e.effects.restore(a,o),e.effects.removeWrapper(a),i()})},e.effects.effect.highlight=function(t,i){var s=e(this),n=["backgroundImage","backgroundColor","opacity"],a=e.effects.setMode(s,t.mode||"show"),o={backgroundColor:s.css("backgroundColor")};"hide"===a&&(o.opacity=0),e.effects.save(s,n),s.show().css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(o,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){"hide"===a&&s.hide(),e.effects.restore(s,n),i()}})},e.effects.effect.size=function(t,i){var s,n,a,o=e(this),r=["position","top","bottom","left","right","width","height","overflow","opacity"],h=["position","top","bottom","left","right","overflow","opacity"],l=["width","height","overflow"],u=["fontSize"],d=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],c=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=e.effects.setMode(o,t.mode||"effect"),f=t.restore||"effect"!==p,m=t.scale||"both",g=t.origin||["middle","center"],v=o.css("position"),y=f?r:h,b={height:0,width:0,outerHeight:0,outerWidth:0};"show"===p&&o.show(),s={height:o.height(),width:o.width(),outerHeight:o.outerHeight(),outerWidth:o.outerWidth()},"toggle"===t.mode&&"show"===p?(o.from=t.to||b,o.to=t.from||s):(o.from=t.from||("show"===p?b:s),o.to=t.to||("hide"===p?b:s)),a={from:{y:o.from.height/s.height,x:o.from.width/s.width},to:{y:o.to.height/s.height,x:o.to.width/s.width}},("box"===m||"both"===m)&&(a.from.y!==a.to.y&&(y=y.concat(d),o.from=e.effects.setTransition(o,d,a.from.y,o.from),o.to=e.effects.setTransition(o,d,a.to.y,o.to)),a.from.x!==a.to.x&&(y=y.concat(c),o.from=e.effects.setTransition(o,c,a.from.x,o.from),o.to=e.effects.setTransition(o,c,a.to.x,o.to))),("content"===m||"both"===m)&&a.from.y!==a.to.y&&(y=y.concat(u).concat(l),o.from=e.effects.setTransition(o,u,a.from.y,o.from),o.to=e.effects.setTransition(o,u,a.to.y,o.to)),e.effects.save(o,y),o.show(),e.effects.createWrapper(o),o.css("overflow","hidden").css(o.from),g&&(n=e.effects.getBaseline(g,s),o.from.top=(s.outerHeight-o.outerHeight())*n.y,o.from.left=(s.outerWidth-o.outerWidth())*n.x,o.to.top=(s.outerHeight-o.to.outerHeight)*n.y,o.to.left=(s.outerWidth-o.to.outerWidth)*n.x),o.css(o.from),("content"===m||"both"===m)&&(d=d.concat(["marginTop","marginBottom"]).concat(u),c=c.concat(["marginLeft","marginRight"]),l=r.concat(d).concat(c),o.find("*[width]").each(function(){var i=e(this),s={height:i.height(),width:i.width(),outerHeight:i.outerHeight(),outerWidth:i.outerWidth()};f&&e.effects.save(i,l),i.from={height:s.height*a.from.y,width:s.width*a.from.x,outerHeight:s.outerHeight*a.from.y,outerWidth:s.outerWidth*a.from.x},i.to={height:s.height*a.to.y,width:s.width*a.to.x,outerHeight:s.height*a.to.y,outerWidth:s.width*a.to.x},a.from.y!==a.to.y&&(i.from=e.effects.setTransition(i,d,a.from.y,i.from),i.to=e.effects.setTransition(i,d,a.to.y,i.to)),a.from.x!==a.to.x&&(i.from=e.effects.setTransition(i,c,a.from.x,i.from),i.to=e.effects.setTransition(i,c,a.to.x,i.to)),i.css(i.from),i.animate(i.to,t.duration,t.easing,function(){f&&e.effects.restore(i,l)})})),o.animate(o.to,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){0===o.to.opacity&&o.css("opacity",o.from.opacity),"hide"===p&&o.hide(),e.effects.restore(o,y),f||("static"===v?o.css({position:"relative",top:o.to.top,left:o.to.left}):e.each(["top","left"],function(e,t){o.css(t,function(t,i){var s=parseInt(i,10),n=e?o.to.left:o.to.top;return"auto"===i?n+"px":s+n+"px"})})),e.effects.removeWrapper(o),i()}})},e.effects.effect.scale=function(t,i){var s=e(this),n=e.extend(!0,{},t),a=e.effects.setMode(s,t.mode||"effect"),o=parseInt(t.percent,10)||(0===parseInt(t.percent,10)?0:"hide"===a?0:100),r=t.direction||"both",h=t.origin,l={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()},u={y:"horizontal"!==r?o/100:1,x:"vertical"!==r?o/100:1};n.effect="size",n.queue=!1,n.complete=i,"effect"!==a&&(n.origin=h||["middle","center"],n.restore=!0),n.from=t.from||("show"===a?{height:0,width:0,outerHeight:0,outerWidth:0}:l),n.to={height:l.height*u.y,width:l.width*u.x,outerHeight:l.outerHeight*u.y,outerWidth:l.outerWidth*u.x},n.fade&&("show"===a&&(n.from.opacity=0,n.to.opacity=1),"hide"===a&&(n.from.opacity=1,n.to.opacity=0)),s.effect(n)},e.effects.effect.puff=function(t,i){var s=e(this),n=e.effects.setMode(s,t.mode||"hide"),a="hide"===n,o=parseInt(t.percent,10)||150,r=o/100,h={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()};e.extend(t,{effect:"scale",queue:!1,fade:!0,mode:n,complete:i,percent:a?o:100,from:a?h:{height:h.height*r,width:h.width*r,outerHeight:h.outerHeight*r,outerWidth:h.outerWidth*r}}),s.effect(t)},e.effects.effect.pulsate=function(t,i){var s,n=e(this),a=e.effects.setMode(n,t.mode||"show"),o="show"===a,r="hide"===a,h=o||"hide"===a,l=2*(t.times||5)+(h?1:0),u=t.duration/l,d=0,c=n.queue(),p=c.length;for((o||!n.is(":visible"))&&(n.css("opacity",0).show(),d=1),s=1;l>s;s++)n.animate({opacity:d},u,t.easing),d=1-d;n.animate({opacity:d},u,t.easing),n.queue(function(){r&&n.hide(),i()}),p>1&&c.splice.apply(c,[1,0].concat(c.splice(p,l+1))),n.dequeue()},e.effects.effect.shake=function(t,i){var s,n=e(this),a=["position","top","bottom","left","right","height","width"],o=e.effects.setMode(n,t.mode||"effect"),r=t.direction||"left",h=t.distance||20,l=t.times||3,u=2*l+1,d=Math.round(t.duration/u),c="up"===r||"down"===r?"top":"left",p="up"===r||"left"===r,f={},m={},g={},v=n.queue(),y=v.length;for(e.effects.save(n,a),n.show(),e.effects.createWrapper(n),f[c]=(p?"-=":"+=")+h,m[c]=(p?"+=":"-=")+2*h,g[c]=(p?"-=":"+=")+2*h,n.animate(f,d,t.easing),s=1;l>s;s++)n.animate(m,d,t.easing).animate(g,d,t.easing);n.animate(m,d,t.easing).animate(f,d/2,t.easing).queue(function(){"hide"===o&&n.hide(),e.effects.restore(n,a),e.effects.removeWrapper(n),i()}),y>1&&v.splice.apply(v,[1,0].concat(v.splice(y,u+1))),n.dequeue()},e.effects.effect.slide=function(t,i){var s,n=e(this),a=["position","top","bottom","left","right","width","height"],o=e.effects.setMode(n,t.mode||"show"),r="show"===o,h=t.direction||"left",l="up"===h||"down"===h?"top":"left",u="up"===h||"left"===h,d={};e.effects.save(n,a),n.show(),s=t.distance||n["top"===l?"outerHeight":"outerWidth"](!0),e.effects.createWrapper(n).css({overflow:"hidden"}),r&&n.css(l,u?isNaN(s)?"-"+s:-s:s),d[l]=(r?u?"+=":"-=":u?"-=":"+=")+s,n.animate(d,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){"hide"===o&&n.hide(),e.effects.restore(n,a),e.effects.removeWrapper(n),i()}})},e.effects.effect.transfer=function(t,i){var s=e(this),n=e(t.to),a="fixed"===n.css("position"),o=e("body"),r=a?o.scrollTop():0,h=a?o.scrollLeft():0,l=n.offset(),u={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},d=s.offset(),c=e("<div class='ui-effects-transfer'></div>").appendTo(document.body).addClass(t.className).css({top:d.top-r,left:d.left-h,height:s.innerHeight(),width:s.innerWidth(),position:a?"fixed":"absolute"}).animate(u,t.duration,t.easing,function(){c.remove(),i()})}});
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.structure.css
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.structure.css	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.structure.css	(revision 2119)
@@ -0,0 +1,833 @@
+/*!
+ * jQuery UI CSS Framework 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/category/theming/
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden {
+	display: none;
+}
+.ui-helper-hidden-accessible {
+	border: 0;
+	clip: rect(0 0 0 0);
+	height: 1px;
+	margin: -1px;
+	overflow: hidden;
+	padding: 0;
+	position: absolute;
+	width: 1px;
+}
+.ui-helper-reset {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	outline: 0;
+	line-height: 1.3;
+	text-decoration: none;
+	font-size: 100%;
+	list-style: none;
+}
+.ui-helper-clearfix:before,
+.ui-helper-clearfix:after {
+	content: "";
+	display: table;
+	border-collapse: collapse;
+}
+.ui-helper-clearfix:after {
+	clear: both;
+}
+.ui-helper-clearfix {
+	min-height: 0; /* support: IE7 */
+}
+.ui-helper-zfix {
+	width: 100%;
+	height: 100%;
+	top: 0;
+	left: 0;
+	position: absolute;
+	opacity: 0;
+	filter:Alpha(Opacity=0); /* support: IE8 */
+}
+
+.ui-front {
+	z-index: 100;
+}
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled {
+	cursor: default !important;
+}
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon {
+	display: block;
+	text-indent: -99999px;
+	overflow: hidden;
+	background-repeat: no-repeat;
+}
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay {
+	position: fixed;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+}
+.ui-draggable-handle {
+	-ms-touch-action: none;
+	touch-action: none;
+}
+.ui-resizable {
+	position: relative;
+}
+.ui-resizable-handle {
+	position: absolute;
+	font-size: 0.1px;
+	display: block;
+	-ms-touch-action: none;
+	touch-action: none;
+}
+.ui-resizable-disabled .ui-resizable-handle,
+.ui-resizable-autohide .ui-resizable-handle {
+	display: none;
+}
+.ui-resizable-n {
+	cursor: n-resize;
+	height: 7px;
+	width: 100%;
+	top: -5px;
+	left: 0;
+}
+.ui-resizable-s {
+	cursor: s-resize;
+	height: 7px;
+	width: 100%;
+	bottom: -5px;
+	left: 0;
+}
+.ui-resizable-e {
+	cursor: e-resize;
+	width: 7px;
+	right: -5px;
+	top: 0;
+	height: 100%;
+}
+.ui-resizable-w {
+	cursor: w-resize;
+	width: 7px;
+	left: -5px;
+	top: 0;
+	height: 100%;
+}
+.ui-resizable-se {
+	cursor: se-resize;
+	width: 12px;
+	height: 12px;
+	right: 1px;
+	bottom: 1px;
+}
+.ui-resizable-sw {
+	cursor: sw-resize;
+	width: 9px;
+	height: 9px;
+	left: -5px;
+	bottom: -5px;
+}
+.ui-resizable-nw {
+	cursor: nw-resize;
+	width: 9px;
+	height: 9px;
+	left: -5px;
+	top: -5px;
+}
+.ui-resizable-ne {
+	cursor: ne-resize;
+	width: 9px;
+	height: 9px;
+	right: -5px;
+	top: -5px;
+}
+.ui-selectable {
+	-ms-touch-action: none;
+	touch-action: none;
+}
+.ui-selectable-helper {
+	position: absolute;
+	z-index: 100;
+	border: 1px dotted black;
+}
+.ui-sortable-handle {
+	-ms-touch-action: none;
+	touch-action: none;
+}
+.ui-accordion .ui-accordion-header {
+	display: block;
+	cursor: pointer;
+	position: relative;
+	margin: 2px 0 0 0;
+	padding: .5em .5em .5em .7em;
+	min-height: 0; /* support: IE7 */
+	font-size: 100%;
+}
+.ui-accordion .ui-accordion-icons {
+	padding-left: 2.2em;
+}
+.ui-accordion .ui-accordion-icons .ui-accordion-icons {
+	padding-left: 2.2em;
+}
+.ui-accordion .ui-accordion-header .ui-accordion-header-icon {
+	position: absolute;
+	left: .5em;
+	top: 50%;
+	margin-top: -8px;
+}
+.ui-accordion .ui-accordion-content {
+	padding: 1em 2.2em;
+	border-top: 0;
+	overflow: auto;
+}
+.ui-autocomplete {
+	position: absolute;
+	top: 0;
+	left: 0;
+	cursor: default;
+}
+.ui-button {
+	display: inline-block;
+	position: relative;
+	padding: 0;
+	line-height: normal;
+	margin-right: .1em;
+	cursor: pointer;
+	vertical-align: middle;
+	text-align: center;
+	overflow: visible; /* removes extra width in IE */
+}
+.ui-button,
+.ui-button:link,
+.ui-button:visited,
+.ui-button:hover,
+.ui-button:active {
+	text-decoration: none;
+}
+/* to make room for the icon, a width needs to be set here */
+.ui-button-icon-only {
+	width: 2.2em;
+}
+/* button elements seem to need a little more width */
+button.ui-button-icon-only {
+	width: 2.4em;
+}
+.ui-button-icons-only {
+	width: 3.4em;
+}
+button.ui-button-icons-only {
+	width: 3.7em;
+}
+
+/* button text element */
+.ui-button .ui-button-text {
+	display: block;
+	line-height: normal;
+}
+.ui-button-text-only .ui-button-text {
+	padding: .4em 1em;
+}
+.ui-button-icon-only .ui-button-text,
+.ui-button-icons-only .ui-button-text {
+	padding: .4em;
+	text-indent: -9999999px;
+}
+.ui-button-text-icon-primary .ui-button-text,
+.ui-button-text-icons .ui-button-text {
+	padding: .4em 1em .4em 2.1em;
+}
+.ui-button-text-icon-secondary .ui-button-text,
+.ui-button-text-icons .ui-button-text {
+	padding: .4em 2.1em .4em 1em;
+}
+.ui-button-text-icons .ui-button-text {
+	padding-left: 2.1em;
+	padding-right: 2.1em;
+}
+/* no icon support for input elements, provide padding by default */
+input.ui-button {
+	padding: .4em 1em;
+}
+
+/* button icon element(s) */
+.ui-button-icon-only .ui-icon,
+.ui-button-text-icon-primary .ui-icon,
+.ui-button-text-icon-secondary .ui-icon,
+.ui-button-text-icons .ui-icon,
+.ui-button-icons-only .ui-icon {
+	position: absolute;
+	top: 50%;
+	margin-top: -8px;
+}
+.ui-button-icon-only .ui-icon {
+	left: 50%;
+	margin-left: -8px;
+}
+.ui-button-text-icon-primary .ui-button-icon-primary,
+.ui-button-text-icons .ui-button-icon-primary,
+.ui-button-icons-only .ui-button-icon-primary {
+	left: .5em;
+}
+.ui-button-text-icon-secondary .ui-button-icon-secondary,
+.ui-button-text-icons .ui-button-icon-secondary,
+.ui-button-icons-only .ui-button-icon-secondary {
+	right: .5em;
+}
+
+/* button sets */
+.ui-buttonset {
+	margin-right: 7px;
+}
+.ui-buttonset .ui-button {
+	margin-left: 0;
+	margin-right: -.3em;
+}
+
+/* workarounds */
+/* reset extra padding in Firefox, see h5bp.com/l */
+input.ui-button::-moz-focus-inner,
+button.ui-button::-moz-focus-inner {
+	border: 0;
+	padding: 0;
+}
+.ui-datepicker {
+	width: 17em;
+	padding: .2em .2em 0;
+	display: none;
+}
+.ui-datepicker .ui-datepicker-header {
+	position: relative;
+	padding: .2em 0;
+}
+.ui-datepicker .ui-datepicker-prev,
+.ui-datepicker .ui-datepicker-next {
+	position: absolute;
+	top: 2px;
+	width: 1.8em;
+	height: 1.8em;
+}
+.ui-datepicker .ui-datepicker-prev-hover,
+.ui-datepicker .ui-datepicker-next-hover {
+	top: 1px;
+}
+.ui-datepicker .ui-datepicker-prev {
+	left: 2px;
+}
+.ui-datepicker .ui-datepicker-next {
+	right: 2px;
+}
+.ui-datepicker .ui-datepicker-prev-hover {
+	left: 1px;
+}
+.ui-datepicker .ui-datepicker-next-hover {
+	right: 1px;
+}
+.ui-datepicker .ui-datepicker-prev span,
+.ui-datepicker .ui-datepicker-next span {
+	display: block;
+	position: absolute;
+	left: 50%;
+	margin-left: -8px;
+	top: 50%;
+	margin-top: -8px;
+}
+.ui-datepicker .ui-datepicker-title {
+	margin: 0 2.3em;
+	line-height: 1.8em;
+	text-align: center;
+}
+.ui-datepicker .ui-datepicker-title select {
+	font-size: 1em;
+	margin: 1px 0;
+}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year {
+	width: 45%;
+}
+.ui-datepicker table {
+	width: 100%;
+	font-size: .9em;
+	border-collapse: collapse;
+	margin: 0 0 .4em;
+}
+.ui-datepicker th {
+	padding: .7em .3em;
+	text-align: center;
+	font-weight: bold;
+	border: 0;
+}
+.ui-datepicker td {
+	border: 0;
+	padding: 1px;
+}
+.ui-datepicker td span,
+.ui-datepicker td a {
+	display: block;
+	padding: .2em;
+	text-align: right;
+	text-decoration: none;
+}
+.ui-datepicker .ui-datepicker-buttonpane {
+	background-image: none;
+	margin: .7em 0 0 0;
+	padding: 0 .2em;
+	border-left: 0;
+	border-right: 0;
+	border-bottom: 0;
+}
+.ui-datepicker .ui-datepicker-buttonpane button {
+	float: right;
+	margin: .5em .2em .4em;
+	cursor: pointer;
+	padding: .2em .6em .3em .6em;
+	width: auto;
+	overflow: visible;
+}
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
+	float: left;
+}
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi {
+	width: auto;
+}
+.ui-datepicker-multi .ui-datepicker-group {
+	float: left;
+}
+.ui-datepicker-multi .ui-datepicker-group table {
+	width: 95%;
+	margin: 0 auto .4em;
+}
+.ui-datepicker-multi-2 .ui-datepicker-group {
+	width: 50%;
+}
+.ui-datepicker-multi-3 .ui-datepicker-group {
+	width: 33.3%;
+}
+.ui-datepicker-multi-4 .ui-datepicker-group {
+	width: 25%;
+}
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
+	border-left-width: 0;
+}
+.ui-datepicker-multi .ui-datepicker-buttonpane {
+	clear: left;
+}
+.ui-datepicker-row-break {
+	clear: both;
+	width: 100%;
+	font-size: 0;
+}
+
+/* RTL support */
+.ui-datepicker-rtl {
+	direction: rtl;
+}
+.ui-datepicker-rtl .ui-datepicker-prev {
+	right: 2px;
+	left: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-next {
+	left: 2px;
+	right: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-prev:hover {
+	right: 1px;
+	left: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-next:hover {
+	left: 1px;
+	right: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane {
+	clear: right;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane button {
+	float: left;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
+.ui-datepicker-rtl .ui-datepicker-group {
+	float: right;
+}
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
+	border-right-width: 0;
+	border-left-width: 1px;
+}
+.ui-dialog {
+	overflow: hidden;
+	position: absolute;
+	top: 0;
+	left: 0;
+	padding: .2em;
+	outline: 0;
+}
+.ui-dialog .ui-dialog-titlebar {
+	padding: .4em 1em;
+	position: relative;
+}
+.ui-dialog .ui-dialog-title {
+	float: left;
+	margin: .1em 0;
+	white-space: nowrap;
+	width: 90%;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+.ui-dialog .ui-dialog-titlebar-close {
+	position: absolute;
+	right: .3em;
+	top: 50%;
+	width: 20px;
+	margin: -10px 0 0 0;
+	padding: 1px;
+	height: 20px;
+}
+.ui-dialog .ui-dialog-content {
+	position: relative;
+	border: 0;
+	padding: .5em 1em;
+	background: none;
+	overflow: auto;
+}
+.ui-dialog .ui-dialog-buttonpane {
+	text-align: left;
+	border-width: 1px 0 0 0;
+	background-image: none;
+	margin-top: .5em;
+	padding: .3em 1em .5em .4em;
+}
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
+	float: right;
+}
+.ui-dialog .ui-dialog-buttonpane button {
+	margin: .5em .4em .5em 0;
+	cursor: pointer;
+}
+.ui-dialog .ui-resizable-se {
+	width: 12px;
+	height: 12px;
+	right: -5px;
+	bottom: -5px;
+	background-position: 16px 16px;
+}
+.ui-draggable .ui-dialog-titlebar {
+	cursor: move;
+}
+.ui-menu {
+	list-style: none;
+	padding: 0;
+	margin: 0;
+	display: block;
+	outline: none;
+}
+.ui-menu .ui-menu {
+	position: absolute;
+}
+.ui-menu .ui-menu-item {
+	position: relative;
+	margin: 0;
+	padding: 3px 1em 3px .4em;
+	cursor: pointer;
+	min-height: 0; /* support: IE7 */
+	/* support: IE10, see #8844 */
+	list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
+}
+.ui-menu .ui-menu-divider {
+	margin: 5px 0;
+	height: 0;
+	font-size: 0;
+	line-height: 0;
+	border-width: 1px 0 0 0;
+}
+.ui-menu .ui-state-focus,
+.ui-menu .ui-state-active {
+	margin: -1px;
+}
+
+/* icon support */
+.ui-menu-icons {
+	position: relative;
+}
+.ui-menu-icons .ui-menu-item {
+	padding-left: 2em;
+}
+
+/* left-aligned */
+.ui-menu .ui-icon {
+	position: absolute;
+	top: 0;
+	bottom: 0;
+	left: .2em;
+	margin: auto 0;
+}
+
+/* right-aligned */
+.ui-menu .ui-menu-icon {
+	left: auto;
+	right: 0;
+}
+.ui-progressbar {
+	height: 2em;
+	text-align: left;
+	overflow: hidden;
+}
+.ui-progressbar .ui-progressbar-value {
+	margin: -1px;
+	height: 100%;
+}
+.ui-progressbar .ui-progressbar-overlay {
+	background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");
+	height: 100%;
+	filter: alpha(opacity=25); /* support: IE8 */
+	opacity: 0.25;
+}
+.ui-progressbar-indeterminate .ui-progressbar-value {
+	background-image: none;
+}
+.ui-selectmenu-menu {
+	padding: 0;
+	margin: 0;
+	position: absolute;
+	top: 0;
+	left: 0;
+	display: none;
+}
+.ui-selectmenu-menu .ui-menu {
+	overflow: auto;
+	/* Support: IE7 */
+	overflow-x: hidden;
+	padding-bottom: 1px;
+}
+.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup {
+	font-size: 1em;
+	font-weight: bold;
+	line-height: 1.5;
+	padding: 2px 0.4em;
+	margin: 0.5em 0 0 0;
+	height: auto;
+	border: 0;
+}
+.ui-selectmenu-open {
+	display: block;
+}
+.ui-selectmenu-button {
+	display: inline-block;
+	overflow: hidden;
+	position: relative;
+	text-decoration: none;
+	cursor: pointer;
+}
+.ui-selectmenu-button span.ui-icon {
+	right: 0.5em;
+	left: auto;
+	margin-top: -8px;
+	position: absolute;
+	top: 50%;
+}
+.ui-selectmenu-button span.ui-selectmenu-text {
+	text-align: left;
+	padding: 0.4em 2.1em 0.4em 1em;
+	display: block;
+	line-height: 1.4;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+.ui-slider {
+	position: relative;
+	text-align: left;
+}
+.ui-slider .ui-slider-handle {
+	position: absolute;
+	z-index: 2;
+	width: 1.2em;
+	height: 1.2em;
+	cursor: default;
+	-ms-touch-action: none;
+	touch-action: none;
+}
+.ui-slider .ui-slider-range {
+	position: absolute;
+	z-index: 1;
+	font-size: .7em;
+	display: block;
+	border: 0;
+	background-position: 0 0;
+}
+
+/* support: IE8 - See #6727 */
+.ui-slider.ui-state-disabled .ui-slider-handle,
+.ui-slider.ui-state-disabled .ui-slider-range {
+	filter: inherit;
+}
+
+.ui-slider-horizontal {
+	height: .8em;
+}
+.ui-slider-horizontal .ui-slider-handle {
+	top: -.3em;
+	margin-left: -.6em;
+}
+.ui-slider-horizontal .ui-slider-range {
+	top: 0;
+	height: 100%;
+}
+.ui-slider-horizontal .ui-slider-range-min {
+	left: 0;
+}
+.ui-slider-horizontal .ui-slider-range-max {
+	right: 0;
+}
+
+.ui-slider-vertical {
+	width: .8em;
+	height: 100px;
+}
+.ui-slider-vertical .ui-slider-handle {
+	left: -.3em;
+	margin-left: 0;
+	margin-bottom: -.6em;
+}
+.ui-slider-vertical .ui-slider-range {
+	left: 0;
+	width: 100%;
+}
+.ui-slider-vertical .ui-slider-range-min {
+	bottom: 0;
+}
+.ui-slider-vertical .ui-slider-range-max {
+	top: 0;
+}
+.ui-spinner {
+	position: relative;
+	display: inline-block;
+	overflow: hidden;
+	padding: 0;
+	vertical-align: middle;
+}
+.ui-spinner-input {
+	border: none;
+	background: none;
+	color: inherit;
+	padding: 0;
+	margin: .2em 0;
+	vertical-align: middle;
+	margin-left: .4em;
+	margin-right: 22px;
+}
+.ui-spinner-button {
+	width: 16px;
+	height: 50%;
+	font-size: .5em;
+	padding: 0;
+	margin: 0;
+	text-align: center;
+	position: absolute;
+	cursor: default;
+	display: block;
+	overflow: hidden;
+	right: 0;
+}
+/* more specificity required here to override default borders */
+.ui-spinner a.ui-spinner-button {
+	border-top: none;
+	border-bottom: none;
+	border-right: none;
+}
+/* vertically center icon */
+.ui-spinner .ui-icon {
+	position: absolute;
+	margin-top: -8px;
+	top: 50%;
+	left: 0;
+}
+.ui-spinner-up {
+	top: 0;
+}
+.ui-spinner-down {
+	bottom: 0;
+}
+
+/* TR overrides */
+.ui-spinner .ui-icon-triangle-1-s {
+	/* need to fix icons sprite */
+	background-position: -65px -16px;
+}
+.ui-tabs {
+	position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+	padding: .2em;
+}
+.ui-tabs .ui-tabs-nav {
+	margin: 0;
+	padding: .2em .2em 0;
+}
+.ui-tabs .ui-tabs-nav li {
+	list-style: none;
+	float: left;
+	position: relative;
+	top: 0;
+	margin: 1px .2em 0 0;
+	border-bottom-width: 0;
+	padding: 0;
+	white-space: nowrap;
+}
+.ui-tabs .ui-tabs-nav .ui-tabs-anchor {
+	float: left;
+	padding: .5em 1em;
+	text-decoration: none;
+}
+.ui-tabs .ui-tabs-nav li.ui-tabs-active {
+	margin-bottom: -1px;
+	padding-bottom: 1px;
+}
+.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,
+.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,
+.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor {
+	cursor: text;
+}
+.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor {
+	cursor: pointer;
+}
+.ui-tabs .ui-tabs-panel {
+	display: block;
+	border-width: 0;
+	padding: 1em 1.4em;
+	background: none;
+}
+.ui-tooltip {
+	padding: 8px;
+	position: absolute;
+	z-index: 9999;
+	max-width: 300px;
+	-webkit-box-shadow: 0 0 5px #aaa;
+	box-shadow: 0 0 5px #aaa;
+}
+body .ui-tooltip {
+	border-width: 2px;
+}
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.structure.min.css
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.structure.min.css	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.structure.min.css	(revision 2119)
@@ -0,0 +1,5 @@
+/*! jQuery UI - v1.11.4 - 2015-04-05
+* http://jqueryui.com
+* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
+
+.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin:2px 0 0 0;padding:.5em .5em .5em .7em;min-height:0;font-size:100%}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{overflow:hidden;position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:none}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{position:relative;margin:0;padding:3px 1em 3px .4em;cursor:pointer;min-height:0;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:bold;line-height:1.5;padding:2px 0.4em;margin:0.5em 0 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-button{display:inline-block;overflow:hidden;position:relative;text-decoration:none;cursor:pointer}.ui-selectmenu-button span.ui-icon{right:0.5em;left:auto;margin-top:-8px;position:absolute;top:50%}.ui-selectmenu-button span.ui-selectmenu-text{text-align:left;padding:0.4em 2.1em 0.4em 1em;display:block;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.theme.css
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.theme.css	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.theme.css	(revision 2119)
@@ -0,0 +1,410 @@
+/*!
+ * jQuery UI CSS Framework 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/category/theming/
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=2191c0&bgTextureHeader=gloss_wave&bgImgOpacityHeader=75&borderColorHeader=4297d7&fcHeader=eaf5f7&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=inset_hard&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=0078ae&bgColorDefault=0078ae&bgTextureDefault=glass&bgImgOpacityDefault=45&borderColorDefault=77d5f7&fcDefault=ffffff&iconColorDefault=e0fdff&bgColorHover=79c9ec&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=448dae&fcHover=026890&iconColorHover=056b93&bgColorActive=6eac2c&bgTextureActive=gloss_wave&bgImgOpacityActive=50&borderColorActive=acdd4a&fcActive=ffffff&iconColorActive=f5e175&bgColorHighlight=f8da4e&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcd113&fcHighlight=915608&iconColorHighlight=f7a50d&bgColorError=e14f1c&bgTextureError=gloss_wave&bgImgOpacityError=45&borderColorError=cd0a0a&fcError=ffffff&iconColorError=fcd113&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=75&opacityOverlay=30&bgColorShadow=999999&bgTextureShadow=flat&bgImgOpacityShadow=55&opacityShadow=45&thicknessShadow=0px&offsetTopShadow=5px&offsetLeftShadow=5px&cornerRadiusShadow=5px
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget {
+	font-family: Verdana,Arial,sans-serif;
+	font-size: 1.1em;
+}
+.ui-widget .ui-widget {
+	font-size: 1em;
+}
+.ui-widget input,
+.ui-widget select,
+.ui-widget textarea,
+.ui-widget button {
+	font-family: Verdana,Arial,sans-serif;
+	font-size: 1em;
+}
+.ui-widget-content {
+	border: 1px solid #a6c9e2;
+	background: #fcfdfd url("images/ui-bg_inset-hard_100_fcfdfd_1x100.png") 50% bottom repeat-x;
+	color: #222222;
+}
+.ui-widget-content a {
+	color: #222222;
+}
+.ui-widget-header {
+	border: 1px solid #4297d7;
+	background: #2191c0 url("images/ui-bg_gloss-wave_75_2191c0_500x100.png") 50% 50% repeat-x;
+	color: #eaf5f7;
+	font-weight: bold;
+}
+.ui-widget-header a {
+	color: #eaf5f7;
+}
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default,
+.ui-widget-content .ui-state-default,
+.ui-widget-header .ui-state-default {
+	border: 1px solid #77d5f7;
+	background: #0078ae url("images/ui-bg_glass_45_0078ae_1x400.png") 50% 50% repeat-x;
+	font-weight: normal;
+	color: #ffffff;
+}
+.ui-state-default a,
+.ui-state-default a:link,
+.ui-state-default a:visited {
+	color: #ffffff;
+	text-decoration: none;
+}
+.ui-state-hover,
+.ui-widget-content .ui-state-hover,
+.ui-widget-header .ui-state-hover,
+.ui-state-focus,
+.ui-widget-content .ui-state-focus,
+.ui-widget-header .ui-state-focus {
+	border: 1px solid #448dae;
+	background: #79c9ec url("images/ui-bg_glass_75_79c9ec_1x400.png") 50% 50% repeat-x;
+	font-weight: normal;
+	color: #026890;
+}
+.ui-state-hover a,
+.ui-state-hover a:hover,
+.ui-state-hover a:link,
+.ui-state-hover a:visited,
+.ui-state-focus a,
+.ui-state-focus a:hover,
+.ui-state-focus a:link,
+.ui-state-focus a:visited {
+	color: #026890;
+	text-decoration: none;
+}
+.ui-state-active,
+.ui-widget-content .ui-state-active,
+.ui-widget-header .ui-state-active {
+	border: 1px solid #acdd4a;
+	background: #6eac2c url("images/ui-bg_gloss-wave_50_6eac2c_500x100.png") 50% 50% repeat-x;
+	font-weight: normal;
+	color: #ffffff;
+}
+.ui-state-active a,
+.ui-state-active a:link,
+.ui-state-active a:visited {
+	color: #ffffff;
+	text-decoration: none;
+}
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight,
+.ui-widget-content .ui-state-highlight,
+.ui-widget-header .ui-state-highlight {
+	border: 1px solid #fcd113;
+	background: #f8da4e url("images/ui-bg_glass_55_f8da4e_1x400.png") 50% 50% repeat-x;
+	color: #915608;
+}
+.ui-state-highlight a,
+.ui-widget-content .ui-state-highlight a,
+.ui-widget-header .ui-state-highlight a {
+	color: #915608;
+}
+.ui-state-error,
+.ui-widget-content .ui-state-error,
+.ui-widget-header .ui-state-error {
+	border: 1px solid #cd0a0a;
+	background: #e14f1c url("images/ui-bg_gloss-wave_45_e14f1c_500x100.png") 50% top repeat-x;
+	color: #ffffff;
+}
+.ui-state-error a,
+.ui-widget-content .ui-state-error a,
+.ui-widget-header .ui-state-error a {
+	color: #ffffff;
+}
+.ui-state-error-text,
+.ui-widget-content .ui-state-error-text,
+.ui-widget-header .ui-state-error-text {
+	color: #ffffff;
+}
+.ui-priority-primary,
+.ui-widget-content .ui-priority-primary,
+.ui-widget-header .ui-priority-primary {
+	font-weight: bold;
+}
+.ui-priority-secondary,
+.ui-widget-content .ui-priority-secondary,
+.ui-widget-header .ui-priority-secondary {
+	opacity: .7;
+	filter:Alpha(Opacity=70); /* support: IE8 */
+	font-weight: normal;
+}
+.ui-state-disabled,
+.ui-widget-content .ui-state-disabled,
+.ui-widget-header .ui-state-disabled {
+	opacity: .35;
+	filter:Alpha(Opacity=35); /* support: IE8 */
+	background-image: none;
+}
+.ui-state-disabled .ui-icon {
+	filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
+}
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon {
+	width: 16px;
+	height: 16px;
+}
+.ui-icon,
+.ui-widget-content .ui-icon {
+	background-image: url("images/ui-icons_0078ae_256x240.png");
+}
+.ui-widget-header .ui-icon {
+	background-image: url("images/ui-icons_d8e7f3_256x240.png");
+}
+.ui-state-default .ui-icon {
+	background-image: url("images/ui-icons_e0fdff_256x240.png");
+}
+.ui-state-hover .ui-icon,
+.ui-state-focus .ui-icon {
+	background-image: url("images/ui-icons_056b93_256x240.png");
+}
+.ui-state-active .ui-icon {
+	background-image: url("images/ui-icons_f5e175_256x240.png");
+}
+.ui-state-highlight .ui-icon {
+	background-image: url("images/ui-icons_f7a50d_256x240.png");
+}
+.ui-state-error .ui-icon,
+.ui-state-error-text .ui-icon {
+	background-image: url("images/ui-icons_fcd113_256x240.png");
+}
+
+/* positioning */
+.ui-icon-blank { background-position: 16px 16px; }
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-on { background-position: -96px -144px; }
+.ui-icon-radio-off { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all,
+.ui-corner-top,
+.ui-corner-left,
+.ui-corner-tl {
+	border-top-left-radius: 5px;
+}
+.ui-corner-all,
+.ui-corner-top,
+.ui-corner-right,
+.ui-corner-tr {
+	border-top-right-radius: 5px;
+}
+.ui-corner-all,
+.ui-corner-bottom,
+.ui-corner-left,
+.ui-corner-bl {
+	border-bottom-left-radius: 5px;
+}
+.ui-corner-all,
+.ui-corner-bottom,
+.ui-corner-right,
+.ui-corner-br {
+	border-bottom-right-radius: 5px;
+}
+
+/* Overlays */
+.ui-widget-overlay {
+	background: #aaaaaa url("images/ui-bg_flat_75_aaaaaa_40x100.png") 50% 50% repeat-x;
+	opacity: .3;
+	filter: Alpha(Opacity=30); /* support: IE8 */
+}
+.ui-widget-shadow {
+	margin: 5px 0 0 5px;
+	padding: 0px;
+	background: #999999 url("images/ui-bg_flat_55_999999_40x100.png") 50% 50% repeat-x;
+	opacity: .45;
+	filter: Alpha(Opacity=45); /* support: IE8 */
+	border-radius: 5px;
+}
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.theme.min.css
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.theme.min.css	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery-ui.theme.min.css	(revision 2119)
@@ -0,0 +1,5 @@
+/*! jQuery UI - v1.11.4 - 2015-04-09
+* http://jqueryui.com
+* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
+
+.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #a6c9e2;background:#fcfdfd url("images/ui-bg_inset-hard_100_fcfdfd_1x100.png") 50% bottom repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #4297d7;background:#2191c0 url("images/ui-bg_gloss-wave_75_2191c0_500x100.png") 50% 50% repeat-x;color:#eaf5f7;font-weight:bold}.ui-widget-header a{color:#eaf5f7}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #77d5f7;background:#0078ae url("images/ui-bg_glass_45_0078ae_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#fff}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#fff;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #448dae;background:#79c9ec url("images/ui-bg_glass_75_79c9ec_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#026890}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#026890;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #acdd4a;background:#6eac2c url("images/ui-bg_gloss-wave_50_6eac2c_500x100.png") 50% 50% repeat-x;font-weight:normal;color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcd113;background:#f8da4e url("images/ui-bg_glass_55_f8da4e_1x400.png") 50% 50% repeat-x;color:#915608}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#915608}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#e14f1c url("images/ui-bg_gloss-wave_45_e14f1c_500x100.png") 50% top repeat-x;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_0078ae_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_d8e7f3_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_e0fdff_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_056b93_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_f5e175_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_f7a50d_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_fcd113_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:5px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:5px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:5px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:5px}.ui-widget-overlay{background:#aaa url("images/ui-bg_flat_75_aaaaaa_40x100.png") 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:5px 0 0 5px;padding:0;background:#999 url("images/ui-bg_flat_55_999999_40x100.png") 50% 50% repeat-x;opacity:.45;filter:Alpha(Opacity=45);border-radius:5px}
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery.orgchart.css
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery.orgchart.css	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery.orgchart.css	(revision 2119)
@@ -0,0 +1,99 @@
+div.orgChart {
+    border                : 1px solid #cccccc;
+    background-color      : #ffffe8;
+    margin                : 10px;
+    padding               : 20px;
+}
+
+div.orgChart h2 {
+    margin                : 0px;
+    font-size             : 1em;
+    border-bottom         : none;
+}
+
+div.orgChart ul {
+    list-style            : none;
+    margin                : 4px;
+    padding               : 0px;
+    font-size             : 0.8em;
+    text-align            : left;
+}
+
+div.orgChart ul.stack,
+div.orgChart ul.stack ul {
+    text-align            : center;
+}
+
+div.orgChart table {
+    width                 : 100%;
+}
+
+div.orgChart tr.lines td.line {
+    width                 : 1px;
+    height                : 20px;
+}
+
+div.orgChart tr.lines td.top {
+    border-top            : 3px solid black;
+}
+
+div.orgChart tr.lines td.left {
+    border-right          : 2px solid black;
+}
+
+div.orgChart tr.lines td.right {
+    border-left           : 2px solid black;
+}
+
+div.orgChart td {
+    text-align            : center;
+    vertical-align        : top;
+    padding               : 0px 2px;
+}
+
+div.orgChart div.node {
+    cursor                : default;
+    border                : 2px solid black;
+    display               : inline-block;
+    padding               : 0 4px;
+    width                 : 96px;
+    height                : 60px;
+    background-color      : #ffffcf;
+    line-height           : 1.3em;
+    border-radius         : 8px;
+    box-shadow            : 4px 4px 6px #888888;
+    -moz-border-radius    : 8px;
+    -moz-box-shadow       : 4px 4px 6px #888888;
+    -webkit-border-radius : 8px;
+    -webkit-box-shadow    : 4px 4px 6px #888888;
+}
+
+div.orgChart div.hasChildren {
+    background-color      : #ffff98;
+}
+
+div.orgChart.interactive div.hasChildren {
+    cursor                : pointer;
+}
+
+div.orgChart div.node.hover {
+    background-color      : white;
+}
+
+div.orgChart div.adjunct.node {
+    position              : absolute;
+    margin-top            : 8px;
+    margin-left           : -110px;
+    width                 : 80px;
+    height                : 40px;
+    background-color      : #efefef;
+}
+
+div.orgChart div.adjunct-link {
+    display               : inline-block;
+    position              : absolute;
+    margin-left           : -20px;
+    margin-top            : 25px;
+    width                 : 20px;
+    border                : 1px dashed black;
+}
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery.orgchart.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery.orgchart.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/jquery/jquery.orgchart.js	(revision 2119)
@@ -0,0 +1,218 @@
+/**
+ * JQuery Organisation Chart Plugin.
+ *
+ * Author: Mark Lee
+ * Copyright (C)2013-2015 Caprica Software Limited
+ * http://www.capricasoftware.co.uk
+ *
+ * Contributions by: Paul Lautman <paul.lautman at gmail.com>
+ *
+ * This software is licensed under the Creative Commons Attribution-ShareAlike 3.0 License,
+ * see here for license terms:
+ *
+ *     http://creativecommons.org/licenses/by-sa/3.0
+ */
+(function($) {
+
+    $.fn.orgChart = function(options) {
+        var opts = $.extend({}, $.fn.orgChart.defaults, options);
+
+        return this.each(function() {
+            var $chartSource = $(this);
+            // Clone the source list hierarchy so levels can be non-destructively removed if needed
+            // before creating the chart
+            $this = $chartSource.clone();
+            if (opts.levels > -1) {
+                $this.find("ul").andSelf().filter(function() {return $chartSource.parents("ul").length+1 > opts.levels;}).remove();
+            }
+            // Store the original element
+            $this.data("chart-source", $chartSource);
+            // Build the chart...
+            var $container = $("<div class='" + opts.chartClass + "'/>");
+            if (opts.interactive) {
+                $container.addClass("interactive");
+            }
+            // The chart may be sourced from either a "ul", or an "li" element...
+            var $root;
+            if ($this.is("ul")) {
+                $root = $this.find("li:first");
+            }
+            else if ($this.is("li")) {
+                $root = $this;
+            }
+            if ($root) {
+                buildNode($root, $container, 0, 0, opts);
+                // Special case for any hyperlink anchor in the chart to prevent the click on the node itself from propagating
+                $container.find("div.node a").click(function(evt) {
+                    evt.stopImmediatePropagation();
+                });
+                if(opts.replace) {
+                    opts.container.empty();
+                }
+                opts.container.append($container);
+            }
+        });
+    };
+
+    $.fn.orgChart.defaults = {
+        container  : $("body"),
+        depth      : -1,
+        levels     : -1,
+        showLevels : -1,
+        stack      : false,
+        chartClass : "orgChart",
+        hoverClass : "hover",
+        nodeText   : function($node) {return $node.clone().children("ul,li").remove().end().html();},
+        interactive: false,
+        fade       : false,
+        speed      : "slow",
+        nodeClicked: function($node) {},
+        copyClasses: true,
+        copyData   : true,
+        copyStyles : true,
+        copyTitle  : true,
+        replace    : true
+    };
+
+    function buildNode($node, $appendTo, level, index, opts) {
+        var $table = $("<table cellpadding='0' cellspacing='0' border='0'/>");
+        var $tbody = $("<tbody/>");
+
+        // Make this node...
+        var $nodeRow = $("<tr/>").addClass("nodes");
+        var $nodeCell = $("<td/>").addClass("node").attr("colspan", 2);
+        var $childNodes = $node.children("ul:first").children("li");
+        if ($childNodes.length > 1) {
+            $nodeCell.attr("colspan", $childNodes.length*2);
+        }
+
+        var $adjunct = $node.children("adjunct").eq(0);
+        if ($adjunct.length > 0) {
+            var $adjunctDiv = $("<div>").addClass("adjunct node").addClass("level"+level).addClass("node"+index).addClass("level"+level+"-node"+index).append(opts.nodeText($adjunct));
+            $adjunctDiv.appendTo($nodeCell);
+            var $linkDiv = $("<div>").addClass("adjunct-link");
+            $linkDiv.appendTo($nodeCell);
+            $adjunct.remove();
+        }
+
+        var $heading = $("<h2>").html(opts.nodeText($node));
+        var $nodeDiv = $("<div>").addClass("node").addClass("level"+level).addClass("node"+index).addClass("level"+level+"-node"+index).append($heading);
+
+        // Copy classes from the source list to the chart node
+        if (opts.copyClasses) {
+            $nodeDiv.addClass($node.attr("class"));
+        }
+
+        // Copy data from the source list to the chart node
+        if (opts.copyData) {
+            $nodeDiv.data($node.data());
+        }
+
+        // Copy CSS styles from the source list to the chart node
+        if (opts.copyStyles) {
+            $nodeDiv.attr("style", $node.attr("style"));
+        }
+
+        // Copy the title attribute from the source list to the chart node
+        if (opts.copyTitle) {
+            $nodeDiv.attr("title", $node.attr("title"));
+        }
+
+        $nodeDiv.data("orgchart-level", level).data("orgchart-node", $node);
+
+        $nodeCell.append($nodeDiv);
+        $nodeRow.append($nodeCell);
+        $tbody.append($nodeRow);
+
+        $nodeDiv.click(function() {
+            var $this = $(this);
+            opts.nodeClicked($this.data("orgchart-node"), $this);
+            if (opts.interactive) {
+                var $row = $this.closest("tr");
+                if ($row.next("tr").is(":visible")) {
+                    if (opts.fade) {
+                        $row.nextAll("tr").fadeOut(opts.speed);
+                    }
+                    else {
+                        $row.nextAll("tr").hide();
+                    }
+                    $this.removeClass("shownChildren").addClass("hiddenChildren");
+                }
+                else {
+                    $this.removeClass("hiddenChildren").addClass("shownChildren");
+                    if (opts.fade) {
+                        $row.nextAll("tr").fadeIn(opts.speed);
+                    }
+                    else {
+                        $row.nextAll("tr").show();
+                    }
+                }
+            }
+        });
+
+        if ($childNodes.length > 0) {
+            if (opts.depth == -1 || level+1 < opts.depth) {
+                var $downLineRow = $("<tr/>").addClass("lines");
+                var $downLineCell = $("<td/>").attr("colspan", $childNodes.length*2);
+                $downLineRow.append($downLineCell);
+
+                var $downLineTable = $("<table cellpadding='0' cellspacing='0' border='0'>");
+                $downLineTable.append("<tbody>");
+                var $downLineLine = $("<tr/>").addClass("lines x");
+                var $downLeft = $("<td>").addClass("line left");
+                var $downRight = $("<td>").addClass("line right");
+                $downLineLine.append($downLeft).append($downRight);
+                $downLineTable.children("tbody").append($downLineLine);
+                $downLineCell.append($downLineTable);
+
+                $tbody.append($downLineRow);
+
+                if ($childNodes.length > 0) {
+                    $nodeDiv.addClass("hasChildren");
+                    if (opts.showLevels == -1 || level < opts.showLevels-1) {
+                        $nodeDiv.addClass("shownChildren");
+                    }
+                    else {
+                        $nodeDiv.addClass("hiddenChildren");
+                    }
+                    if (opts.interactive) {
+                        $nodeDiv.hover(function() {$(this).addClass(opts.hoverClass);}, function() {$(this).removeClass(opts.hoverClass)});
+                    }
+                }
+
+                // Recursively make child nodes...
+                var $linesRow = $("<tr/>").addClass("lines v");
+                $childNodes.each(function() {
+                    var $left = $("<td/>").addClass("line left top");
+                    var $right = $("<td/>").addClass("line right top");
+                    $linesRow.append($left).append($right);
+                });
+                $linesRow.find("td:first").removeClass("top");
+                $linesRow.find("td:last").removeClass("top");
+                $tbody.append($linesRow);
+                var $childNodesRow = $("<tr/>");
+                $childNodes.each(function(index) {
+                    var $td = $("<td/>");
+                    $td.attr("colspan", 2);
+                    buildNode($(this), $td, level+1, index, opts);
+                    $childNodesRow.append($td);
+                });
+                $tbody.append($childNodesRow);
+            }
+            else if (opts.stack) {
+                var $stackNodes = $childNodes.clone();
+                var $list = $("<ul class='stack'>").append($stackNodes).addClass("level"+level).addClass("node"+index).addClass("level"+level+"-node"+index);
+                var $stackContainer = $("<div class='stack-container'>").append($list);
+                $nodeDiv.after($stackContainer);
+            }
+        }
+
+        if (opts.showLevels > -1 && level >= opts.showLevels-1) {
+            $nodeRow.nextAll("tr").hide();
+        }
+
+        $table.append($tbody);
+        $appendTo.append($table);
+    };
+
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/js/timeline/timeframe.js
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/js/timeline/timeframe.js	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/js/timeline/timeframe.js	(revision 2119)
@@ -0,0 +1,405 @@
+(function ($) {
+
+	/////////////////////
+	// MAIN ENTRY POINT
+	/////////////////////
+
+	var Timeframe = function (selector) {
+		var element = $(selector);
+
+		var timeline = new Timeline(element);
+		return timeline;
+	};
+	window.Timeframe = Timeframe;
+
+	var defaultOptions = {
+		tickHeight: 8,
+		textHeight: 10
+	};
+
+	///////////////////
+	// TIMELINE CLASS
+	///////////////////
+
+	var Timeline = function (container) {
+		this.container = container;
+		this.categories = [];
+
+		this.options = {};
+		for (var i in defaultOptions) {
+			this.options[i] = defaultOptions[i];
+		}
+
+		this.outerWidth = this.container.innerWidth();
+		this.outerHeight = 140;
+		this.paddingX = 40;
+		this.paddingY = 60;
+
+		this.width = this.outerWidth - (this.paddingX * 2);
+		this.height = this.outerHeight - (this.paddingY * 2);
+
+		this.container.height(this.outerHeight);
+
+		this.svg = $(svg("svg"))
+			.attr("width", this.outerWidth)
+			.attr("height", this.outerHeight)
+			.appendTo(this.container);
+	};
+
+	// Configuration
+
+	Timeline.prototype.start = function (startString) {
+		this.startDate = parseDateString(startString);
+		return this;
+	};
+
+	Timeline.prototype.end = function (endString) {
+		this.endDate = parseDateString(endString);
+		return this;
+	};
+
+	Timeline.prototype.majorTicks = function (number, type) {
+		this.options.majorTicks = this.options.majorTicks || {};
+		this.options.majorTicks.number = number;
+		this.options.majorTicks.type = type;
+		return this;
+	};
+
+	Timeline.prototype.minorTicks = function (number, type) {
+		this.options.minorTicks = this.options.minorTicks || {};
+		this.options.minorTicks.number = number;
+		this.options.minorTicks.type = type;
+		return this;
+	};
+
+	Timeline.prototype.autoTicks = function () {
+		//TODO: look at the range of dates and adjust accordingly
+		this.majorTicks(5, "years");
+		this.minorTicks(1, "year");
+		return this;
+	};
+
+	// Adding items
+
+	Timeline.prototype.addCategory = function (categoryData) {
+		console.log("addCategory", categoryData);
+
+		var category = {
+			name: categoryData.name,
+			color: categoryData.color,
+			events: [],
+			spans: []
+		};
+		this.categories.push(category);
+
+		if (categoryData.events && categoryData.events.length) {
+			for (var e = 0; e < categoryData.events.length; e++) {
+				var event = categoryData.events[e];
+				this.addEvent(event, category);
+			}
+		}
+
+		if (categoryData.spans && categoryData.spans.length) {
+			for (var s = 0; s < categoryData.spans.length; s++) {
+				var span = categoryData.spans[s];
+				this.addSpan(span, category);
+			}
+		}
+
+		return this;
+	};
+
+	Timeline.prototype.addEvent = function (eventData, category) {
+		var event = {
+			name:    eventData.name,
+			date:    parseDateString(eventData.date),
+            options: eventData.options ? eventData.options : {},
+		};
+		category.events.push(event);
+	};
+
+	Timeline.prototype.addSpan = function (spanData, category) {
+		var span = {
+			name: spanData.name,
+            color: spanData.color,
+			start: parseDateString(spanData.start),
+			end: parseDateString(spanData.end)
+		};
+		category.spans.push(span);
+	};
+
+	// Rendering
+
+	Timeline.prototype.draw = function () {
+
+		//Prepare to draw first...
+		if (!this.options.majorTicks || !this.options.minorTicks) {
+			this.autoTicks();
+		}
+
+		this.drawBackground();
+		this.drawItems();
+
+		return this;
+	};
+
+	Timeline.prototype.drawBackground = function () {
+
+		var background = $svg.group()
+			.attr("class", "background")
+			.appendTo(this.svg);
+
+		var tickHeight = this.options.tickHeight;
+		var textHeight = this.options.textHeight;
+
+		var bottomLine = $svg.line(
+			this.paddingX,
+			this.height + this.paddingY - textHeight - tickHeight - 0.5,
+			this.width + this.paddingX,
+			this.height + this.paddingY - textHeight - tickHeight - 0.5
+		)
+			.attr("stroke", "#000000")
+			.attr("stroke-width", 1)
+			.appendTo(background);
+
+		//Draw the labels and ticks for each year
+		for (var year = this.startDate.getFullYear(); year <= this.endDate.getFullYear(); year++) {
+			var yearDate = new Date(year, 0, 1);
+			var x = Math.floor(this.getX(yearDate)) + 0.5;
+
+			var isMajorTick = (year % this.options.majorTicks.number === 0);
+
+			if (isMajorTick) {
+				var yearLabel = $svg.text(
+					x,
+					this.height + this.paddingY,
+					year,
+					{
+						fill: "black",
+						"font-size": "12",
+						"text-anchor": "middle"
+					}
+				)
+					.appendTo(background);
+			}
+
+			var yearTick = $svg.line(
+				x,
+				this.height + this.paddingY - textHeight - tickHeight,
+				x,
+				this.height + this.paddingY - textHeight
+			)
+				.attr("stroke", "#000000")
+				.attr("stroke-width", 1)
+				.appendTo(background);
+		}
+		
+		return this;
+	};
+
+	Timeline.prototype.drawItems = function () {
+		for (var c = 0; c < this.categories.length; c++) {
+			var category = this.categories[c];
+
+			for (var e = 0; e < category.events.length; e++) {
+				this.drawEvent(category.events[e]);
+			}
+
+			for (var s = 0; s < category.spans.length; s++) {
+				this.drawSpan(category.spans[s]);
+			}
+		}
+		return this;
+	};
+
+	Timeline.prototype.drawEvent = function (event) {
+		var x = this.getX(event.date);
+
+		var group = $svg.group()
+			.attr("class", "event")
+			.appendTo(this.svg);
+
+		var circle = $svg.circle(
+			x,
+			this.height - this.options.textHeight - this.options.tickHeight + this.paddingY,
+			5
+		)
+			.attr("fill", "black")
+			.appendTo(group);
+
+        var color = 
+              event.options.type == 'alarm'      ? 'red'
+            : event.options.type == 'employment' ? 'green'
+            : 'blue';
+        var labelHeigth = event.options.type == 'alarm'
+            ? 32
+            : 12;
+        var line = $svg.line(
+           x,
+           this.height - this.options.textHeight - this.options.tickHeight + this.paddingY,
+           x,
+           this.height - this.options.textHeight - this.options.tickHeight + this.paddingY - labelHeigth
+        )
+            .attr("stroke", color)
+            .attr("stroke-width", 1)
+            .appendTo(group); 
+
+		var label = $svg.text(
+			x,
+			this.height - this.options.textHeight - this.options.tickHeight + this.paddingY - (labelHeigth + 8),
+			event.name,
+			{
+				fill: color,
+				"font-size": "12",
+				"text-anchor": "middle"
+			}
+		)
+			.appendTo(group);
+
+        if (event.options.showdate) {
+            var line = $svg.line(
+                x,
+                this.height - this.options.textHeight - this.options.tickHeight + this.paddingY,
+                x,
+                this.height - this.options.textHeight - this.options.tickHeight + this.paddingY + 16
+            )
+                .attr("stroke", color)
+                .attr("stroke-width", 1)
+                .appendTo(group);
+
+            var label = $svg.text(
+                x,
+                this.height - this.options.textHeight - this.options.tickHeight + this.paddingY + 24,
+                $.datepicker.formatDate('dd/mm/yy', event.date),
+                {
+                    fill: color,
+                    "font-size": "11",
+                    "text-anchor": "middle"
+                }
+            )
+                .appendTo(group);
+        }
+	};
+
+	Timeline.prototype.drawSpan = function (span) {
+		console.log("drawSpan", span);
+
+		var startX = this.getX(span.start);
+		var endX = this.getX(span.end);
+
+		var group = $svg.group()
+			.attr("class", "span")
+			.appendTo(this.svg);
+
+		var rect = $svg.rect(
+			startX,
+			this.height - this.options.textHeight - this.options.tickHeight + this.paddingY - 4,
+			endX - startX,
+			7,
+			{
+				rx: 5,
+				ry: 5
+			}
+		)
+			.attr("fill", span.color)
+			.appendTo(group);
+
+		var label = $svg.text(
+			(startX + endX) / 2,
+			this.height + this.options.textHeight - this.options.tickHeight + this.paddingY + 24,
+			span.name,
+			{
+				fill: "black",
+				"font-size": "12",
+				"text-anchor": "middle"
+			}
+		)
+			.appendTo(group);
+	};
+
+	// Utilities
+
+	Timeline.prototype.getX = function (date) {
+		var dateValue = date.valueOf();
+		var start = this.startDate.valueOf();
+		var end = this.endDate.valueOf();
+
+		var x = ((dateValue - start) / (end - start) * this.width) + this.paddingX;
+		return x;
+	};
+
+	////////////
+	// HELPERS
+	////////////
+
+	var parseDateString = function (dateString) {
+		//TODO: make this more robust
+		var pieces = dateString.split("-");
+
+		var years = parseInt(pieces[0], 10);
+		var months = parseInt(pieces[1], 10) - 1;	//months are 0-based
+		var days = parseInt(pieces[2], 10);
+
+		var date = new Date(years, months, days);
+		return date;
+	};
+
+	var $svg = {};
+
+	$svg.group = function () {
+		var element = $(svg("g"));
+		return element;
+	};
+
+	$svg.line = function (x1, y1, x2, y2, options) {
+		var element = $(svg("line"))
+			.attr("x1", x1)
+			.attr("y1", y1)
+			.attr("x2", x2)
+			.attr("y2", y2);
+		setSvgOptions(element, options);
+		return element;
+	};
+
+	$svg.circle = function (cx, cy, r, options) {
+		var element = $(svg("circle"))
+			.attr("cx", cx)
+			.attr("cy", cy)
+			.attr("r", r);
+		setSvgOptions(element, options);
+		return element;
+	};
+
+	$svg.rect = function (x, y, width, height, options) {
+		var element = $(svg("rect"))
+			.attr("x", x)
+			.attr("y", y)
+			.attr("width", width)
+			.attr("height", height);
+		setSvgOptions(element, options);
+		return element;
+	};
+
+	$svg.text = function (x, y, text, options) {
+		var element = $(svg("text"))
+			.attr("x", x)
+			.attr("y", y)
+			.text(text);
+		setSvgOptions(element, options);
+		return element;
+	};
+
+	function svg(tagName)
+	{
+		return document.createElementNS('http://www.w3.org/2000/svg', tagName);
+	}
+
+	function setSvgOptions(element, options) {
+		if (options) {
+			for (var i in options) {
+				element.attr(i, options[i]);
+			}
+		}
+	}
+
+})(jQuery);
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/struct.css
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/struct.css	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/struct.css	(revision 2119)
@@ -0,0 +1,67 @@
+/* $Id$ */
+
+#header  {
+    width: auto;
+    padding: 0.2em;
+}
+
+#sidebar {
+    font-size: 0.9em;
+}
+
+#sidebar [name="q"] {
+    width: 130px;
+}
+
+#sidebar form {
+    display: inline-block;
+}
+
+#sidebar>span {
+    padding-left: 20px;
+}
+
+#mainbox {
+    width: auto;
+    padding: 1em;
+    border: groove 10px;
+}
+
+#oinfo {
+}
+
+#oaction {
+    clear: both;
+}
+#oaction form {
+    display: inline-block;
+}
+
+#omenu {
+    clear: both;
+}
+
+#objectform {
+    clear: both;
+}
+
+#objectform div {
+    vertical-align: text-top;
+}
+#objectform form {
+    display: inline-block;
+    vertical-align: text-top;
+}
+#objectform table {
+    display: inline-block;
+    vertical-align: text-top;
+}
+
+#footer {
+    clear: both;
+    padding: 0.2em;
+}
+
+#navigate {
+    float: right;
+}
Index: tags/5.2.23/LATMOS-Accounts-Web/root/static/style.css
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/root/static/style.css	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/root/static/style.css	(revision 2119)
@@ -0,0 +1,300 @@
+/* global dcl: */
+
+img {
+    border: none;
+    vertical-align: middle;
+    padding: 0em
+}
+
+input {
+    font-size: 0.95em;
+}
+
+.ui-autocomplete {
+     max-height: 8em;
+     overflow-y: auto;
+     /* prevent horizontal scrollbar */
+     overflow-x: hidden;
+}
+
+.container {
+    margin: 20px auto;
+    max-width: 800px;
+}
+
+.ui-menu:after {
+    content: ".";
+    display: block;
+    clear: both;
+    visibility: hidden;
+    line-height: 0;
+    height: 0;
+}
+
+.error {
+    color: red;
+}
+
+#menu {
+    width: auto;
+}
+
+#menu>.ui-menu-item {
+    border: 1px solid #A6C9E2;
+    width: auto;
+    display: inline-block;
+    float: left;
+}
+
+.ui-menu {
+    margin: 0;
+    padding: 0;
+    width: 150px;
+}
+
+* html .ui-autocomplete {
+     height: 8em;
+}
+
+
+input[type=image] {
+    border: outset;
+    border-color: black;
+    vertical-align: middle;
+    background-color: #DDDDD9;
+}
+#oaction input[type=image] {
+    width: 1.5em;
+    height:1.5em;
+    vertical-align: middle;
+}
+img.editobject {
+    width: 1.5em;
+    height:1.5em;
+    vertical-align: middle;
+}
+img.notice {
+    width: 1em;
+    height:1em;
+    vertical-align: middle;
+}
+img.attr {
+    width: 1.5em;
+    height:1.5em;
+    vertical-align: middle;
+}
+input.attr {
+    width: 1.5em;
+    height:1.5em;
+    vertical-align: middle;
+    font-size: 1em;
+}
+/* Button like */
+img.button {
+    border: outset;
+    border-color: black;
+}
+input.button {
+    border: outset;
+    border-color: black;
+}
+img.attrbutton {
+    border: outset;
+    border-color: black;
+    width: 1.5em;
+    height:1.5em;
+    vertical-align: middle;
+}
+input.attrbutton {
+    border: outset;
+    border-color: black;
+    width: 1.5em;
+    height:1.5em;
+    vertical-align: middle;
+}
+
+.unexported {
+    text-decoration: line-through;
+    color: grey;
+}
+
+.bloc-box {
+    display: inline-block;
+    padding: 2px;
+    margin: auto;
+    vertical-align: middle;
+}
+
+#search-adv-filter {
+    width: 100%;
+}
+
+div#search-adv-filter {
+}
+
+#header {
+    padding: 1px;
+}
+#header h1 {
+    text-align: center;
+    font-size: 1.2em;
+    margin: 0px;
+    padding: 0em;
+    background-color: #7DB4D8;
+}
+
+#mainbox {
+    margin-top:    0.2em;
+    margin-bottom: 0.2em;
+}
+
+#oinfo p {
+    padding: 0em;
+    margin: 0em;
+    margin-left: 1em;
+}
+#oinfo p#oname {
+    text-transform: capitalize;
+    font-size: 1.2em;
+}
+#oinfo_icons {
+    float: left;
+}
+#oname img {
+    width: 1.5em;
+    height:1.5em;
+    vertical-align: middle;
+}
+#oinfo_label {
+    float: left;
+}
+#navigate {
+    margin: 0em;
+    padding: 0em;
+}
+#navigate p {
+    margin: 0em;
+    text-align: center;
+}
+
+/* Object menu: */
+
+#omenu a {
+    text-decoration: none;
+    color: black;
+}
+#omenu div {
+    padding-top: 0.5em;
+    padding-bottom: 0.5em;
+    padding-left: 0em;
+    margin: 0em;
+}
+#omenu span {
+    padding-left: 0.5em;
+    padding-right: 0.5em;
+    margin: 0em;
+    border: groove thin;
+    text-decoration: none;
+    float: left;
+    background-color: #DDDDD9;
+    color: black;
+}
+#omenu span#oactive {
+    padding-left: 0.5em;
+    padding-right: 0.5em;
+    margin: 0em;
+    border: groove thin;
+    text-decoration: none;
+    float: left;
+    background-color: #7DB4D8;
+    color: black;
+}
+#omenu a:hover {
+    text-color blue;
+}
+
+
+#user_group_form {
+    max-height: 15em;
+    width: 15em;
+    overflow: auto;
+    border: inset;
+}
+#user_group_form ul {
+    list-style-type: none;
+    padding: 0.2em;
+    font-size: 0.9em;
+}
+
+#user_address_list {
+    border: outset;
+}
+#user_address_list td {
+    padding-bottom: 0.5em;
+    padding-top:    0.5em;
+}
+#user_address_list td.user_address_cel {
+    padding-bottom: 0.5em;
+    padding-top:    0.5em;
+    padding-right:  1em;
+}
+
+.datatable table, .datatable th, .datatable td  {
+    border: 1px solid black;
+    font-size: 0.8em;
+}
+
+.datatable th, .datatable td  {
+    padding-top: 0.3em;
+    padding-bottom: 0.3em;
+    padding-left: 0.5em;
+    padding-right: 0.5em;
+}
+
+.datatable td {
+    text-align: right;
+}
+
+.datalist table, .datalist th, .datalist td  {
+    border: 1px solid black;
+    font-size: 0.8em;
+}
+
+.datalist th, .datalist td  {
+    padding-top: 0.3em;
+    padding-bottom: 0.3em;
+    padding-left: 0.5em;
+    padding-right: 0.5em;
+    vertical-align: top;
+}
+
+.datalist td {
+    text-align: left;
+}
+
+.active tr, .active td {
+    font-weight: bold;
+}
+
+/* box receiving random password */
+span#randpasswd {
+    padding: 0.3em;
+    margin: 0.3em;
+    border: inset;
+    font-family: monospace;
+    font-size: 1.5em;
+    background-color: black;
+    color: green;
+}
+
+/* Random password options list */
+ul#randpasswd_options {
+    list-style-type: none;
+    padding-top: 0.2em;
+    padding-left: 0em;
+} 
+
+div#objectform {
+    padding-top: 1em;
+    padding-bottom: 1em;
+}
Index: tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_cgi.pl
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_cgi.pl	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_cgi.pl	(revision 2119)
@@ -0,0 +1,37 @@
+#!/usr/bin/perl -w
+
+BEGIN { $ENV{CATALYST_ENGINE} ||= 'CGI' }
+
+use strict;
+use warnings;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use LATMOS::Accounts::Web;
+
+LATMOS::Accounts::Web->run;
+
+1;
+
+=head1 NAME
+
+latmos_accounts_web_cgi.pl - Catalyst CGI
+
+=head1 SYNOPSIS
+
+See L<Catalyst::Manual>
+
+=head1 DESCRIPTION
+
+Run a Catalyst application as a cgi script.
+
+=head1 AUTHOR
+
+Sebastian Riedel, C<sri@oook.de>
+
+=head1 COPYRIGHT
+
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_create.pl
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_create.pl	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_create.pl	(revision 2119)
@@ -0,0 +1,74 @@
+#!/usr/bin/perl -w
+
+use strict;
+use warnings;
+use Getopt::Long;
+use Pod::Usage;
+use Catalyst::Helper;
+
+my $force = 0;
+my $mech  = 0;
+my $help  = 0;
+
+GetOptions(
+    'nonew|force'    => \$force,
+    'mech|mechanize' => \$mech,
+    'help|?'         => \$help
+ );
+
+pod2usage(1) if ( $help || !$ARGV[0] );
+
+my $helper = Catalyst::Helper->new( { '.newfiles' => !$force, mech => $mech } );
+
+pod2usage(1) unless $helper->mk_component( 'LATMOS::Accounts::Web', @ARGV );
+
+1;
+
+=head1 NAME
+
+latmos_accounts_web_create.pl - Create a new Catalyst Component
+
+=head1 SYNOPSIS
+
+latmos_accounts_web_create.pl [options] model|view|controller name [helper] [options]
+
+ Options:
+   -force        don't create a .new file where a file to be created exists
+   -mechanize    use Test::WWW::Mechanize::Catalyst for tests if available
+   -help         display this help and exits
+
+ Examples:
+   latmos_accounts_web_create.pl controller My::Controller
+   latmos_accounts_web_create.pl -mechanize controller My::Controller
+   latmos_accounts_web_create.pl view My::View
+   latmos_accounts_web_create.pl view MyView TT
+   latmos_accounts_web_create.pl view TT TT
+   latmos_accounts_web_create.pl model My::Model
+   latmos_accounts_web_create.pl model SomeDB DBIC::Schema MyApp::Schema create=dynamic\
+   dbi:SQLite:/tmp/my.db
+   latmos_accounts_web_create.pl model AnotherDB DBIC::Schema MyApp::Schema create=static\
+   dbi:Pg:dbname=foo root 4321
+
+ See also:
+   perldoc Catalyst::Manual
+   perldoc Catalyst::Manual::Intro
+
+=head1 DESCRIPTION
+
+Create a new Catalyst Component.
+
+Existing component files are not overwritten.  If any of the component files
+to be created already exist the file will be written with a '.new' suffix.
+This behavior can be suppressed with the C<-force> option.
+
+=head1 AUTHOR
+
+Sebastian Riedel, C<sri@oook.de>
+Maintained by the Catalyst Core Team.
+
+=head1 COPYRIGHT
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_fastcgi.pl
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_fastcgi.pl	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_fastcgi.pl	(revision 2119)
@@ -0,0 +1,80 @@
+#!/usr/bin/perl -w
+
+BEGIN { $ENV{CATALYST_ENGINE} ||= 'FastCGI' }
+
+use strict;
+use warnings;
+use Getopt::Long;
+use Pod::Usage;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use LATMOS::Accounts::Web;
+
+my $help = 0;
+my ( $listen, $nproc, $pidfile, $manager, $detach, $keep_stderr );
+ 
+GetOptions(
+    'help|?'      => \$help,
+    'listen|l=s'  => \$listen,
+    'nproc|n=i'   => \$nproc,
+    'pidfile|p=s' => \$pidfile,
+    'manager|M=s' => \$manager,
+    'daemon|d'    => \$detach,
+    'keeperr|e'   => \$keep_stderr,
+);
+
+pod2usage(1) if $help;
+
+LATMOS::Accounts::Web->run( 
+    $listen, 
+    {   nproc   => $nproc,
+        pidfile => $pidfile, 
+        manager => $manager,
+        detach  => $detach,
+	keep_stderr => $keep_stderr,
+    }
+);
+
+1;
+
+=head1 NAME
+
+latmos_accounts_web_fastcgi.pl - Catalyst FastCGI
+
+=head1 SYNOPSIS
+
+latmos_accounts_web_fastcgi.pl [options]
+ 
+ Options:
+   -? -help      display this help and exits
+   -l -listen    Socket path to listen on
+                 (defaults to standard input)
+                 can be HOST:PORT, :PORT or a
+                 filesystem path
+   -n -nproc     specify number of processes to keep
+                 to serve requests (defaults to 1,
+                 requires -listen)
+   -p -pidfile   specify filename for pid file
+                 (requires -listen)
+   -d -daemon    daemonize (requires -listen)
+   -M -manager   specify alternate process manager
+                 (FCGI::ProcManager sub-class)
+                 or empty string to disable
+   -e -keeperr   send error messages to STDOUT, not
+                 to the webserver
+
+=head1 DESCRIPTION
+
+Run a Catalyst application as fastcgi.
+
+=head1 AUTHOR
+
+Sebastian Riedel, C<sri@oook.de>
+Maintained by the Catalyst Core Team.
+
+=head1 COPYRIGHT
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_server.pl
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_server.pl	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_server.pl	(revision 2119)
@@ -0,0 +1,131 @@
+#!/usr/bin/perl -w
+
+BEGIN { 
+    $ENV{CATALYST_ENGINE} ||= 'HTTP';
+    $ENV{CATALYST_SCRIPT_GEN} = 30;
+    require Catalyst::Engine::HTTP;
+}  
+
+use strict;
+use warnings;
+use Getopt::Long;
+use Pod::Usage;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+
+my $debug             = 0;
+my $daemon            = 0;
+my $fork              = 0;
+my $help              = 0;
+my $host              = undef;
+my $port              = 3000;
+my $keepalive         = 0;
+my $restart           = 0;
+my $restart_delay     = 1;
+my $restart_regex     = '\.yml$|\.yaml$|\.pm$';
+my $restart_directory = undef;
+my $forcehttps        = 0;
+
+my @argv = @ARGV;
+
+GetOptions(
+    'debug|d'             => \$debug,
+    'fork'                => \$fork,
+    'daemon'              => \$daemon,
+    'help|?'              => \$help,
+    'host=s'              => \$host,
+    'port=s'              => \$port,
+    'keepalive|k'         => \$keepalive,
+    'restart|r'           => \$restart,
+    'restartdelay|rd=s'   => \$restart_delay,
+    'restartregex|rr=s'   => \$restart_regex,
+    'restartdirectory=s'  => \$restart_directory,
+    'forcehttps'          => \$forcehttps,
+);
+
+pod2usage(1) if $help;
+
+if ( $restart ) {
+    $ENV{CATALYST_ENGINE} = 'HTTP::Restarter';
+}
+if ( $debug ) {
+    $ENV{CATALYST_DEBUG} = 1;
+}
+if ( $forcehttps ) {
+    $ENV{HTTPS} = 'ON';
+}
+
+if ($daemon) {
+    require POSIX;
+    my $childpid = fork;
+    if ($childpid) {
+       exit(0);
+    }
+    # child now
+    open STDIN,  "+</dev/null" or die $!;
+    open STDOUT, ">&STDIN"     or die $!;
+    open STDERR, ">&STDIN"     or die $!;
+}
+
+# This is require instead of use so that the above environment
+# variables can be set at runtime.
+require LATMOS::Accounts::Web;
+
+LATMOS::Accounts::Web->run( $port, $host, {
+    argv              => \@argv,
+    'fork'            => $fork,
+    keepalive         => $keepalive,
+    restart           => $restart,
+    restart_delay     => $restart_delay,
+    restart_regex     => qr/$restart_regex/,
+    restart_directory => $restart_directory,
+} );
+
+1;
+
+=head1 NAME
+
+latmos_accounts_web_server.pl - Catalyst Testserver
+
+=head1 SYNOPSIS
+
+latmos_accounts_web_server.pl [options]
+
+ Options:
+   -d -debug          force debug mode
+   -f -fork           handle each request in a new process
+                      (defaults to false)
+   -? -help           display this help and exits
+      -host           host (defaults to all)
+   -p -port           port (defaults to 3000)
+   -k -keepalive      enable keep-alive connections
+   -r -restart        restart when files get modified
+                      (defaults to false)
+   -rd -restartdelay  delay between file checks
+   -rr -restartregex  regex match files that trigger
+                      a restart when modified
+                      (defaults to '\.yml$|\.yaml$|\.pm$')
+   -restartdirectory  the directory to search for
+                      modified files
+                      (defaults to '../')
+   -forcehttps        force url to https
+
+ See also:
+   perldoc Catalyst::Manual
+   perldoc Catalyst::Manual::Intro
+
+=head1 DESCRIPTION
+
+Run a Catalyst Testserver for this application.
+
+=head1 AUTHOR
+
+Sebastian Riedel, C<sri@oook.de>
+Maintained by the Catalyst Core Team.
+
+=head1 COPYRIGHT
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_test.pl
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_test.pl	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/script/latmos_accounts_web_test.pl	(revision 2119)
@@ -0,0 +1,54 @@
+#!/usr/bin/perl -w
+
+use strict;
+use warnings;
+use Getopt::Long;
+use Pod::Usage;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Catalyst::Test 'LATMOS::Accounts::Web';
+
+my $help = 0;
+
+GetOptions( 'help|?' => \$help );
+
+pod2usage(1) if ( $help || !$ARGV[0] );
+
+print request($ARGV[0])->content . "\n";
+
+1;
+
+=head1 NAME
+
+latmos_accounts_web_test.pl - Catalyst Test
+
+=head1 SYNOPSIS
+
+latmos_accounts_web_test.pl [options] uri
+
+ Options:
+   -help    display this help and exits
+
+ Examples:
+   latmos_accounts_web_test.pl http://localhost/some_action
+   latmos_accounts_web_test.pl /some_action
+
+ See also:
+   perldoc Catalyst::Manual
+   perldoc Catalyst::Manual::Intro
+
+=head1 DESCRIPTION
+
+Run a Catalyst action from the command line.
+
+=head1 AUTHOR
+
+Sebastian Riedel, C<sri@oook.de>
+Maintained by the Catalyst Core Team.
+
+=head1 COPYRIGHT
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts-Web/t/01app.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/01app.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/01app.t	(revision 2119)
@@ -0,0 +1,7 @@
+use strict;
+use warnings;
+use Test::More tests => 2;
+
+BEGIN { use_ok 'Catalyst::Test', 'LATMOS::Accounts::Web' }
+
+ok( request('/')->is_success, 'Request should succeed' );
Index: tags/5.2.23/LATMOS-Accounts-Web/t/02pod.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/02pod.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/02pod.t	(revision 2119)
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+use Test::More;
+
+eval "use Test::Pod 1.14";
+plan skip_all => 'Test::Pod 1.14 required' if $@;
+plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD};
+
+all_pod_files_ok();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/03podcoverage.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/03podcoverage.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/03podcoverage.t	(revision 2119)
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+use Test::More;
+
+eval "use Test::Pod::Coverage 1.04";
+plan skip_all => 'Test::Pod::Coverage 1.04 required' if $@;
+plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD};
+
+all_pod_coverage_ok();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/04config_load.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/04config_load.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/04config_load.t	(revision 2119)
@@ -0,0 +1,11 @@
+# $Id$
+
+use strict;
+use warnings;
+use Test::More tests => 1;
+use YAML;
+
+ok(
+    my $hashref = YAML::LoadFile('etc/latmos-accounts-web.yml'),
+    'Can load the default config'
+);
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_About.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_About.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_About.t	(revision 2119)
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+use Test::More;
+
+BEGIN { use_ok 'Catalyst::Test', 'LATMOS::Accounts::Web' }
+BEGIN { use_ok 'LATMOS::Accounts::Web::Controller::About' }
+
+ok( request('/about')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Ajax.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Ajax.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Ajax.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More tests => 3;
+
+BEGIN { use_ok 'Catalyst::Test', 'LATMOS::Accounts::Web' }
+BEGIN { use_ok 'LATMOS::Accounts::Web::Controller::Ajax' }
+
+ok( request('/ajax')->is_success, 'Request should succeed' );
+
+
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Aliases.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Aliases.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Aliases.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More tests => 3;
+
+BEGIN { use_ok 'Catalyst::Test', 'LATMOS::Accounts::Web' }
+BEGIN { use_ok 'LATMOS::Accounts::Web::Controller::Aliases' }
+
+ok( request('/aliases')->is_success, 'Request should succeed' );
+
+
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Create.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Create.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Create.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More tests => 3;
+
+BEGIN { use_ok 'Catalyst::Test', 'LATMOS::Accounts::Web' }
+BEGIN { use_ok 'LATMOS::Accounts::Web::Controller::Create' }
+
+ok( request('/create')->is_success, 'Request should succeed' );
+
+
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Groups.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Groups.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Groups.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More tests => 3;
+
+BEGIN { use_ok 'Catalyst::Test', 'LATMOS::Accounts::Web' }
+BEGIN { use_ok 'LATMOS::Accounts::Web::Controller::Groups' }
+
+ok( request('/groups')->is_success, 'Request should succeed' );
+
+
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-DpmtRH.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-DpmtRH.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-DpmtRH.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Json::DpmtRH;
+
+ok( request('/json/dpmtrh')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-Employment.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-Employment.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-Employment.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Json::Employment;
+
+ok( request('/json/employment')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-EmploymentByPeriod.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-EmploymentByPeriod.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-EmploymentByPeriod.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Json::EmploymentByPeriod;
+
+ok( request('/json/employmentbyperiod')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-Expire.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-Expire.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-Expire.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Json::Expire;
+
+ok( request('/json/expire')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-List.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-List.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-List.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Json::List;
+
+ok( request('/json/list')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-RefVal.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-RefVal.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-RefVal.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Json::RefVal;
+
+ok( request('/json/refval')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-Stat.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-Stat.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-Stat.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Json::Stat;
+
+ok( request('/json/stat')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-Timeline.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-Timeline.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json-Timeline.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Json::Timeline;
+
+ok( request('/json/timeline')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Json.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Json;
+
+ok( request('/json')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Logout.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Logout.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Logout.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Logout;
+
+ok( request('/logout')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Logs.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Logs.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Logs.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Logs;
+
+ok( request('/logs')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-AskPwReset.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-AskPwReset.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-AskPwReset.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Remote::AskPwReset;
+
+ok( request('/remote/askpwreset')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-Listing.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-Listing.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-Listing.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Remote::Listing;
+
+ok( request('/remote/listing?otype=user')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-Obj.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-Obj.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-Obj.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Remote::Obj;
+
+ok( request('/remote/obj')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-PwReset.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-PwReset.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-PwReset.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Remote::PwReset;
+
+ok( request('/remote/pwreset')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-Search.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-Search.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote-Search.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Remote::Search;
+
+ok( request('/remote/search?otype=user')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Remote.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Remote;
+
+ok( request('/remote')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Search-Adv.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Search-Adv.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Search-Adv.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Search::Adv;
+
+ok( request('/search/adv')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Search.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Search.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Search.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Search;
+
+ok( request('/search')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Sites.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Sites.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Sites.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More tests => 3;
+
+BEGIN { use_ok 'Catalyst::Test', 'LATMOS::Accounts::Web' }
+BEGIN { use_ok 'LATMOS::Accounts::Web::Controller::Sites' }
+
+ok( request('/sites')->is_success, 'Request should succeed' );
+
+
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Summary-Employment.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Summary-Employment.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Summary-Employment.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Summary::Employment;
+
+ok( request('/summary/employment')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Summary-EmploymentByPeriod.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Summary-EmploymentByPeriod.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Summary-EmploymentByPeriod.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Summary::EmploymentByPeriod;
+
+ok( request('/summary/employmentbyperiod')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Summary-Offices.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Summary-Offices.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Summary-Offices.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Summary::Offices;
+
+ok( request('/summary/offices')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Users-Employment.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Users-Employment.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Users-Employment.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+use Catalyst::Test 'LATMOS::Accounts::Web';
+use LATMOS::Accounts::Web::Controller::Users::Employment;
+
+ok( request('/users/employment')->is_success, 'Request should succeed' );
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/controller_Users.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/controller_Users.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/controller_Users.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More tests => 3;
+
+BEGIN { use_ok 'Catalyst::Test', 'LATMOS::Accounts::Web' }
+BEGIN { use_ok 'LATMOS::Accounts::Web::Controller::Users' }
+
+ok( request('/users')->is_success, 'Request should succeed' );
+
+
Index: tags/5.2.23/LATMOS-Accounts-Web/t/model_Accounts.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/model_Accounts.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/model_Accounts.t	(revision 2119)
@@ -0,0 +1,6 @@
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+BEGIN { use_ok 'LATMOS::Accounts::Web::Model::Accounts' }
+
Index: tags/5.2.23/LATMOS-Accounts-Web/t/model_AttrForms.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/model_AttrForms.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/model_AttrForms.t	(revision 2119)
@@ -0,0 +1,6 @@
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+BEGIN { use_ok 'LATMOS::Accounts::Web::Model::AttrForms' }
+
Index: tags/5.2.23/LATMOS-Accounts-Web/t/view_Json.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/view_Json.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/view_Json.t	(revision 2119)
@@ -0,0 +1,8 @@
+use strict;
+use warnings;
+use Test::More;
+
+
+BEGIN { use_ok 'LATMOS::Accounts::Web::View::Json' }
+
+done_testing();
Index: tags/5.2.23/LATMOS-Accounts-Web/t/view_TT.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/view_TT.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/view_TT.t	(revision 2119)
@@ -0,0 +1,6 @@
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+BEGIN { use_ok 'LATMOS::Accounts::Web::View::TT' }
+
Index: tags/5.2.23/LATMOS-Accounts-Web/t/view_TTpart.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts-Web/t/view_TTpart.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts-Web/t/view_TTpart.t	(revision 2119)
@@ -0,0 +1,6 @@
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+BEGIN { use_ok 'LATMOS::Accounts::Web::View::TTpart' }
+
Index: tags/5.2.23/LATMOS-Accounts/Changes
===================================================================
--- tags/5.2.23/LATMOS-Accounts/Changes	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/Changes	(revision 2119)
@@ -0,0 +1,42 @@
+5.2.23
+    - add physicalDeliveryOfficeName attribute to nethost
+    - add netLocked attribute to nethost: when set localhost IP address as given
+      by DHCP zone to the computer to deny is to connect to network 
+
+5.2.20
+    - Allow to limit attribute value from database settings
+    - Make netZone attribute for nethost searchable
+
+5.2.19
+    - Allow to use a specific cracklib dictionnary 
+
+5.2.16
+    - Allow to choose crypt() algorythm using crypt_method setting in
+      latmos-accounts.ini, see man latmos-accounts.ini
+    - Add radius mac-address authentication based support: build users list as
+      file for freeradius via netzone
+    - Add inventory number support
+
+5.2.15
+    - When creating object related object can be directly
+      created at the same time (man latmos-accounts: "INPUT FILE" section)
+    - Improve code to suggest collision avoidance login at user creation
+    - Add la-gen-passwd as installed script
+    - Add task module to disable computer when user reach inactive state
+      (Unusedhosts)
+    - allow to diable task from config (disable=yes in la-sync-manager.ini)
+    - Add task module Iprecover: delete IP from unexported hosts
+
+5.2.14
+
+    Allow to set manager at user creation
+
+5.2.13
+
+    Bug fix for 5.2.12
+
+5.2.12
+
+    Allow to set subject in expiration mail
+    Rework Employment code
+    Add ssh-key attribute to user: LA-Tools/bin/rla-sshkey can be used to deploy it
Index: tags/5.2.23/LATMOS-Accounts/MANIFEST
===================================================================
--- tags/5.2.23/LATMOS-Accounts/MANIFEST	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/MANIFEST	(revision 2119)
@@ -0,0 +1,206 @@
+Changes
+MANIFEST
+MANIFEST.SKIP
+Makefile.PL
+README
+TODO
+annexes/sql.schema.sql
+bin/la-acls
+bin/la-attributes
+bin/la-ban-passwd
+bin/la-cli
+bin/la-config
+bin/la-create
+bin/la-delete
+bin/la-edit
+bin/la-expired-reminder
+bin/la-gen-passwd
+bin/la-graph.pl
+bin/la-group
+bin/la-guser
+bin/la-load-csv
+bin/la-log-test
+bin/la-passwd
+bin/la-qacls
+bin/la-query
+bin/la-rename
+bin/la-renewEmp
+bin/la-search
+bin/la-sql-alias
+bin/la-sql-attrvalues
+bin/la-sql-crypt-passwd
+bin/la-sql-edit-form
+bin/la-sql-exchange-hostname
+bin/la-sql-exchange-ip
+bin/la-sql-find-expired
+bin/la-sql-freeip
+bin/la-sql-log
+bin/la-sql-properties
+bin/la-sql-recompute
+bin/la-sql-rename-host
+bin/la-sql-reset-passwd
+bin/la-sql-rev
+bin/la-sql-runstat
+bin/la-sql-stat2csv
+bin/la-sql-switch-cname
+bin/la-sql-updsshfp
+bin/la-sql-upgrade
+bin/la-sync
+bin/la-sync-ctl
+bin/la-sync-manager
+bin/la-sync-process
+bin/la-test-mail
+bin/la-warn-expire
+bin/la2xls
+etc/cron.d/latmos-accounts
+etc/init.d/la-sync-manager
+etc/sysconfig/latmos-accounts
+latmos-accounts.spec.in
+lib/LATMOS/Accounts.pm
+lib/LATMOS/Accounts/Acls.pm
+lib/LATMOS/Accounts/Acls/Acl.pm
+lib/LATMOS/Accounts/Bases.pm
+lib/LATMOS/Accounts/Bases/Ad.pm
+lib/LATMOS/Accounts/Bases/Ad/Group.pm
+lib/LATMOS/Accounts/Bases/Ad/User.pm
+lib/LATMOS/Accounts/Bases/Ad/objects.pm
+lib/LATMOS/Accounts/Bases/Attributes.pm
+lib/LATMOS/Accounts/Bases/Dummy.pm
+lib/LATMOS/Accounts/Bases/Dummy/User.pm
+lib/LATMOS/Accounts/Bases/Heimdal.pm
+lib/LATMOS/Accounts/Bases/Heimdal/User.pm
+lib/LATMOS/Accounts/Bases/Ldap.pm
+lib/LATMOS/Accounts/Bases/Ldap/Aliases.pm
+lib/LATMOS/Accounts/Bases/Ldap/Group.pm
+lib/LATMOS/Accounts/Bases/Ldap/Onlyaddress.pm
+lib/LATMOS/Accounts/Bases/Ldap/User.pm
+lib/LATMOS/Accounts/Bases/Ldap/objects.pm
+lib/LATMOS/Accounts/Bases/Mail.pm
+lib/LATMOS/Accounts/Bases/Mail/Aliases.pm
+lib/LATMOS/Accounts/Bases/Mail/Revaliases.pm
+lib/LATMOS/Accounts/Bases/Mail/objects.pm
+lib/LATMOS/Accounts/Bases/OCHelper.pm
+lib/LATMOS/Accounts/Bases/OCHelper/Group.pm
+lib/LATMOS/Accounts/Bases/OCHelper/Nethost.pm
+lib/LATMOS/Accounts/Bases/OCHelper/User.pm
+lib/LATMOS/Accounts/Bases/Objects.pm
+lib/LATMOS/Accounts/Bases/Sql.pm
+lib/LATMOS/Accounts/Bases/Sql/Address.pm
+lib/LATMOS/Accounts/Bases/Sql/Aliases.pm
+lib/LATMOS/Accounts/Bases/Sql/Employment.pm
+lib/LATMOS/Accounts/Bases/Sql/Group.pm
+lib/LATMOS/Accounts/Bases/Sql/Nethost.pm
+lib/LATMOS/Accounts/Bases/Sql/Netzone.pm
+lib/LATMOS/Accounts/Bases/Sql/Onlyaddress.pm
+lib/LATMOS/Accounts/Bases/Sql/Revaliases.pm
+lib/LATMOS/Accounts/Bases/Sql/Service.pm
+lib/LATMOS/Accounts/Bases/Sql/Site.pm
+lib/LATMOS/Accounts/Bases/Sql/Stat.pm
+lib/LATMOS/Accounts/Bases/Sql/Sutype.pm
+lib/LATMOS/Accounts/Bases/Sql/User.pm
+lib/LATMOS/Accounts/Bases/Sql/Userstatus.pm
+lib/LATMOS/Accounts/Bases/Sql/objects.pm
+lib/LATMOS/Accounts/Bases/Sql/upgrade.pm
+lib/LATMOS/Accounts/Bases/Unix.pm
+lib/LATMOS/Accounts/Bases/Unix/Group.pm
+lib/LATMOS/Accounts/Bases/Unix/User.pm
+lib/LATMOS/Accounts/Bases/Zimbra.pm
+lib/LATMOS/Accounts/Bases/Zimbra/User.pm
+lib/LATMOS/Accounts/Cli.pm
+lib/LATMOS/Accounts/I18N.pm
+lib/LATMOS/Accounts/Log.pm
+lib/LATMOS/Accounts/Mail.pm
+lib/LATMOS/Accounts/Maintenance.pm
+lib/LATMOS/Accounts/SyncManager.pm
+lib/LATMOS/Accounts/SynchAccess.pm
+lib/LATMOS/Accounts/SynchAccess/Objects.pm
+lib/LATMOS/Accounts/SynchAccess/base.pm
+lib/LATMOS/Accounts/Synchro.pm
+lib/LATMOS/Accounts/Task.pm
+lib/LATMOS/Accounts/Task/Basessynchro.pm
+lib/LATMOS/Accounts/Task/Buildlistes.pm
+lib/LATMOS/Accounts/Task/Buildnet.pm
+lib/LATMOS/Accounts/Task/Delexpiredusers.pm
+lib/LATMOS/Accounts/Task/Dummy.pm
+lib/LATMOS/Accounts/Task/Employment.pm
+lib/LATMOS/Accounts/Task/Expiredaliases.pm
+lib/LATMOS/Accounts/Task/Iprecover.pm
+lib/LATMOS/Accounts/Task/Refreshexpired.pm
+lib/LATMOS/Accounts/Task/Stats.pm
+lib/LATMOS/Accounts/Task/Unexportexpired.pm
+lib/LATMOS/Accounts/Task/Unusedhosts.pm
+lib/LATMOS/Accounts/Task/Updatedyndata.pm
+lib/LATMOS/Accounts/Utils.pm
+live-test/10_sql.t
+live-test/20_ldap.t
+live-test/30_ad.t
+live-test/40_heimdal.t
+live-test/90_scripts_run.t
+live-test/91_scripts_run_help.t
+live-test/common.pl
+live-test/config.pl.sample
+man/man5/la-allowed-values.ini.pod
+man/man5/la-sync-list.ini.pod
+man/man5/la-sync-manager.ini.pod
+man/man5/latmos-accounts.ini.pod
+man/man8/latmos-accounts-base-ad.pod
+man/man8/latmos-accounts-base-ldap.pod
+man/man8/latmos-accounts-base-sql.pod
+man/man8/latmos-accounts-base-unix.pod
+man/man8/latmos-accounts-base-zimbra.pod
+man/man8/latmos-accounts-changes.pod
+man/man8/latmos-accounts-faq.pod
+man/man8/latmos-accounts.pod
+patchset/no_useless_load.patch
+po/fr.po
+po/messages.pot
+sample/allowed_values.ini
+sample/la-allowed-values.ini
+sample/la-sync-list.ini
+sample/la-sync-manager.ini
+sample/latmos-accounts.ini
+sample/list.cfg
+scripts/ochelper
+scripts/pod2xhtml.pl
+sqldata/base.sql
+t/01_pod.t
+t/01_pod_coverage.t
+t/02_LATMOS-Accounts.t
+t/05_utils.t
+t/06_cli.t
+t/10_bases.t
+t/11_bases_unix.t
+t/12_bases_sql.t
+t/13_bases_ldap.t
+t/14_bases_ad.t
+t/15_bases_mail.t
+t/16_bases_heimdal.t
+t/20_accounts.t
+t/21_acls.t
+t/22_accounts_attributes.t
+t/23_ochelper.t
+t/25_la_synchro.t
+t/26_la_synchaccess.t
+t/30_la_task.t
+t/scripts.pl
+templates/mail/account_expire.mail
+templates/mail/account_expired_reminder.mail
+templates/mail/passwordreset.mail
+testdata/acls1
+testdata/acls2
+testdata/aliases
+testdata/config
+testdata/configdir/latmos-accounts.ini
+testdata/group
+testdata/gshadow
+testdata/passwd
+testdata/revaliases
+testdata/shadow
+testdata/synchro/dest/group
+testdata/synchro/dest/gshadow
+testdata/synchro/dest/passwd
+testdata/synchro/dest/shadow
+testdata/synchro/source/group
+testdata/synchro/source/gshadow
+testdata/synchro/source/passwd
+testdata/synchro/source/shadow
Index: tags/5.2.23/LATMOS-Accounts/MANIFEST.SKIP
===================================================================
--- tags/5.2.23/LATMOS-Accounts/MANIFEST.SKIP	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/MANIFEST.SKIP	(revision 2119)
@@ -0,0 +1,5 @@
+.*\.rpm$
+.*~$
+^blib/.*
+^latmos-accounts.spec$
+^config/
Index: tags/5.2.23/LATMOS-Accounts/Makefile.PL
===================================================================
--- tags/5.2.23/LATMOS-Accounts/Makefile.PL	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/Makefile.PL	(revision 2119)
@@ -0,0 +1,261 @@
+use 5.010000;
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+
+my @po;
+foreach (glob('po/*.po')) {
+    my ($po) = $_ =~  m:([^/\.]+)\.po$:;
+    push (@po, $po);
+}
+
+WriteMakefile(
+    NAME              => 'LATMOS::Accounts',
+    VERSION_FROM      => 'lib/LATMOS/Accounts.pm',
+    PREREQ_PM         => {
+        'Net::LDAP' => undef,
+        'Unicode::Map8' => undef,
+        'DBD::Pg' => undef,
+        'Crypt::RSA' => undef,
+        'MIME::Base64' => undef,
+        'Term::ReadKey' => undef,
+        'Config::IniFiles' => undef,
+        'Mail::Sendmail' => undef,
+        'Net::DNS' => undef,
+        'Template' => undef,
+        'Sys::Syslog' => undef,
+        'Pod::Usage' => undef,
+        'Unicode::String' => undef,
+        'Crypt::Cracklib' => undef,
+        'Crypt::Blowfish' => undef,
+        'Net::IP' => undef,
+        'Net::IPv4Addr' => undef,
+        'Net::IPv6Addr' => undef,
+        'DateTime' => undef,
+        'SOAP::Lite' => undef,
+        'HTTP::Cookies' => undef,
+        'XML::XPath' => undef,
+        'Crypt::SmbHash' => undef,
+        'Date::Calc' => undef,
+        'Spreadsheet::WriteExcel' => undef,
+        'Text::CSV' => undef,
+    }, # e.g., Module::Name => 1.1
+    ($] >= 5.005 ?     ## Add these new keywords supported since 5.005
+      (ABSTRACT_FROM  => 'lib/LATMOS/Accounts.pm', # retrieve abstract from module
+       AUTHOR         => 'Thauvin Olivier <olivier.thauvin@latmos.ipsl.fr>') : ()),
+    EXE_FILES => [ qw(
+        bin/la-acls
+        bin/la-qacls
+        bin/la-attributes
+        bin/la-cli
+        bin/la-config
+        bin/la-create
+        bin/la-sql-crypt-passwd
+        bin/la-delete
+        bin/la-edit
+        bin/la-sql-find-expired
+        bin/la-group
+        bin/la-guser
+        bin/la-passwd
+        bin/la-gen-passwd
+        bin/la-query
+        bin/la-rename
+        bin/la-sql-rename-host
+        bin/la-sql-exchange-ip
+        bin/la-sql-exchange-hostname
+        bin/la-sql-switch-cname
+        bin/la-sql-rev
+        bin/la-search
+        bin/la-sync
+        bin/la-sync-manager
+        bin/la-sync-ctl
+        bin/la-warn-expire
+        bin/la-sql-freeip
+        bin/la-expired-reminder
+        bin/la-sql-upgrade
+        bin/la-sql-recompute
+        bin/la-sql-edit-form
+        bin/la-sql-updsshfp
+        bin/la-test-mail
+        bin/la-sql-log
+        bin/la-sql-runstat
+        bin/la-sql-properties
+        bin/la-sql-alias
+        bin/la-sql-stat2csv
+        bin/la2xls
+        bin/la-ban-passwd
+        bin/la-renewEmp
+        bin/la-load-csv
+        bin/la-sql-attrvalues
+        ) ],
+        macro => {
+            INSTALLMAN5DIR      => '$(PERLPREFIX)/share/man/man5',
+            INSTALLMAN8DIR      => '$(PERLPREFIX)/share/man/man8',
+            DESTINSTALLMAN5DIR  => '$(DESTDIR)$(INSTALLMAN5DIR)',
+            DESTINSTALLMAN8DIR  => '$(DESTDIR)$(INSTALLMAN8DIR)',
+            LOCALEDIR           => '/usr/share/locale',
+            INST_MAN5DIR        => 'blib/man5',
+            INST_MAN8DIR        => 'blib/man8',
+            DESTRPMDIR          => '$(shell pwd)',
+            DATADIR             => '/usr/share/latmos-accounts',
+            DESTDATADIR         => '$(DESTDIR)$(DATADIR)',
+            MAN1PODSBIN         => join(' ', <bin/*>),
+            MAN5PODS            => join(' ', <man/man5/*.pod>),
+            MAN8PODS            => join(' ', <man/man8/*.pod>),
+        },
+        MAN1PODS        => {
+            map {
+                my $targ = $_;
+                $targ =~ s{^man/man(\d)}{};
+                my $section = $1 || 1;
+                $targ =~ s{^bin/}{};
+                $targ =~ s/.pod$//;
+                ( $_ =>
+                    "\$(INST_MAN${section}DIR)/$targ.$section" );
+            } <man/man?/*.pod>, (grep { ! /~$/ } <bin/*>)
+        },
+);
+
+package MY;
+
+sub postamble {
+
+    my $makepo = '';
+
+    foreach my $po (@po) {
+        $makepo .=
+"
+po/$po.po: po/messages.pot
+\t[ -f po/$po.po ] || msginit --no-translator --input=po/messages.pot --output= --locale=$_
+\tmsgmerge -U po/$po.po po/messages.pot
+
+po/$po/LC_MESSAGES/linkaccounts.mo: po/$po.po
+\tinstall -d po/$po/LC_MESSAGES
+\tmsgfmt -o \$@ \$<
+\n"
+    }
+
+    $makepo .= "\n\npo: " . join(' ', map { "po/$_.po" } @po) . "\n";
+    $makepo .= "\n\nmo: " . join(' ', map { "po/$_/LC_MESSAGES/linkaccounts.mo" } @po) . "\n";
+
+    <<EOF;
+.PHONY: po/messages.pot
+
+pot: po/messages.pot
+
+po/messages.pot:
+\tcat MANIFEST | egrep '^(lib|bin)' | xargs xgettext.pl -o po/messages.pot
+
+$makepo
+
+svnmanifest:
+\tsvn ls -R| grep -v "/\$\$"  > MANIFEST
+
+ChangeLog:
+\tsvn log > ChangeLog
+
+latmos-accounts.spec: latmos-accounts.spec.in Makefile
+\tsed -e 's/\@VERSION@/\$(VERSION)/' < \$< > \$@
+
+rpm: \$(DISTVNAME).tar.gz latmos-accounts.spec
+\tmkdir ./noarch || :
+\trpmbuild -ba --clean\\
+\t --define "_sourcedir `pwd`" \\
+\t --define "_specdir `pwd`" \\
+\t --define "_srcrpmdir \$(DESTRPMDIR)" \\
+\t --define "_rpmdir \$(DESTRPMDIR)" \\
+\t latmos-accounts.spec
+
+svnrpm: \$(DISTVNAME).tar.gz latmos-accounts.spec
+\tmkdir \$(DESTRPMDIR)/noarch || :
+\trpmbuild -ba --clean\\
+\t --define "_sourcedir `pwd`" \\
+\t --define "_specdir `pwd`" \\
+\t --define "_srcrpmdir \$(DESTRPMDIR)" \\
+\t --define "_rpmdir \$(DESTRPMDIR)" \\
+\t --define "svnrelease `LC_ALL=C svn info | grep '^Revision:' | sed 's/Revision: //'`" \\
+\t latmos-accounts.spec
+
+podhtml: \$(MAN5PODS) \$(MAN8PODS) \$(MAN3PODS) \$(MAN1PODSBIN)
+\tmkdir html || :
+\trm -fr html/*
+\tperl scripts/pod2xhtml.pl man1 \$(MAN1PODSBIN)
+\tperl scripts/pod2xhtml.pl man3 \$(MAN3PODS)
+\tperl scripts/pod2xhtml.pl man5 \$(MAN5PODS)
+\tperl scripts/pod2xhtml.pl man8 \$(MAN8PODS)
+
+EOF
+}
+
+sub installbin {
+    my $self = shift;
+    my $inherited = $self->SUPER::installbin(@_);
+    my $s = join '|', map quotemeta, @sbin_scripts;
+    # how to create needed directories under blib
+    $inherited .= $self->dir_target("\$(INST_$_)") for qw(MAN5DIR MAN8DIR);
+    $inherited;
+}
+
+sub top_targets {
+    my $inherited = shift->SUPER::top_targets(@_);
+    $inherited =~ s/^config ::/$& \$(INST_MAN5DIR)\$(DIRFILESEP).exists \$(INST_MAN8DIR)\$(DIRFILESEP).exists/m;
+    $inherited;
+}
+
+sub install {
+    my ($self) = @_;
+    my $section = $self->SUPER::install();
+
+    $section =~ s/(^install ::.*)/$1 install_config install_templates install_data install_mo/m;
+    $section =~ s/\$\(INST_BIN\) \$\(DESTINSTALL(\w*)BIN\)/$& \$(INST_MAN5DIR) \$(DESTINSTALLMAN5DIR) \$(INST_MAN8DIR) \$(DESTINSTALLMAN8DIR)/g;
+
+    $section .= q[
+install_config ::
+	install -d $(DESTDIR)/etc
+	install -d $(DESTDIR)/etc/latmos-accounts
+	install sample/latmos-accounts.ini $(DESTDIR)/etc/latmos-accounts/latmos-accounts.ini
+	install sample/la-sync-list.ini $(DESTDIR)/etc/latmos-accounts/la-sync-list.ini
+	install sample/la-allowed-values.ini $(DESTDIR)/etc/latmos-accounts/la-allowed-values.ini
+	install sample/la-sync-manager.ini $(DESTDIR)/etc/latmos-accounts/la-sync-manager.ini
+
+install_templates ::
+	install -d $(DESTDIR)$(DATADIR)/templates
+	$(ABSPERLRUN) -MExtUtils::Install -e 'install({ templates => @ARGV })' \\
+		$(DESTDIR)$(DATADIR)/templates
+
+install_data ::
+	install -d $(DESTDIR)$(DATADIR)
+	$(ABSPERLRUN) -MExtUtils::Install -e 'install({ sqldata => @ARGV })' \\
+	    $(DESTDIR)$(DATADIR)
+
+install_mo: mo
+	install -d $(DESTDIR)$(LOCALEDIR)
+];
+
+    foreach (@po) {
+        $section .= "\tinstall -d \$(DESTDIR)\$(LOCALEDIR)/$_/LC_MESSAGES\n";
+        $section .= "\tinstall po/$_/LC_MESSAGES/linkaccounts.mo \$(DESTDIR)\$(LOCALEDIR)/$_/LC_MESSAGES/linkaccounts.mo\n";
+    }
+    $section .= "\n";
+  
+    return $section;
+}
+
+sub test {
+    my $inherited = shift->SUPER::test(@_);
+
+    $inherited =~ s:^(test_dynamic .*):$1 test_scripts:m;
+
+    $inherited .= <<EOF;
+
+TESTLIVE_FILES = live-test/*.t
+TESTSCRIPTS_FILE = t/scripts.pl
+
+test_live :: pure_all
+\tPERL_DL_NONLAZY=1 \$(FULLPERLRUN) "-MExtUtils::Command::MM" "-e" "test_harness(\$(TEST_VERBOSE), '\$(INST_LIB)', '\$(INST_ARCHLIB)')" \$(TESTLIVE_FILES)
+
+test_scripts :: pure_all
+\tPERL_DL_NONLAZY=1 \$(FULLPERLRUN) "-I\$(INST_LIB)" "-I\$(INST_ARCHLIB)" \$(TESTSCRIPTS_FILE) \$(EXE_FILES)
+EOF
+    return $inherited;
+}
Index: tags/5.2.23/LATMOS-Accounts/README
===================================================================
--- tags/5.2.23/LATMOS-Accounts/README	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/README	(revision 2119)
@@ -0,0 +1,40 @@
+LATMOS-Accounts version 0.01
+============================
+
+The README is used to introduce the module and provide instructions on
+how to install the module, any machine dependencies it may have (for
+example C compilers and installed libraries) and any other information
+that should be provided before the module is installed.
+
+A README file is required for CPAN modules since CPAN extracts the
+README file from a module distribution so that people browsing the
+archive can use it get an idea of the modules uses. It is usually a
+good idea to provide version information here so that people can
+decide whether fixes for the module are worth downloading.
+
+INSTALLATION
+
+To install this module type the following:
+
+   perl Makefile.PL
+   make
+   make test
+   make install
+
+DEPENDENCIES
+
+This module requires these other modules and libraries:
+
+  blah blah blah
+
+COPYRIGHT AND LICENCE
+
+Put the correct copyright and licence information here.
+
+Copyright (C) 2009 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
Index: tags/5.2.23/LATMOS-Accounts/TODO
===================================================================
--- tags/5.2.23/LATMOS-Accounts/TODO	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/TODO	(revision 2119)
@@ -0,0 +1,4 @@
+$Id$
+
+- fix la-rename when one of base don't support the object type
+- move task from cron to la-sync-manager
Index: tags/5.2.23/LATMOS-Accounts/annexes/sql.schema.sql
===================================================================
--- tags/5.2.23/LATMOS-Accounts/annexes/sql.schema.sql	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/annexes/sql.schema.sql	(revision 2119)
@@ -0,0 +1,270 @@
+--
+-- PostgreSQL database dump
+--
+
+SET client_encoding = 'UTF8';
+SET standard_conforming_strings = off;
+SET check_function_bodies = false;
+SET client_min_messages = warning;
+SET escape_string_warning = off;
+
+--
+-- Name: plpgsql; Type: PROCEDURAL LANGUAGE; Schema: -; Owner: -
+--
+
+CREATE PROCEDURAL LANGUAGE plpgsql;
+
+
+SET search_path = public, pg_catalog;
+
+--
+-- Name: rev_tg_f(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION rev_tg_f() RETURNS "trigger"
+    AS $$begin
+
+IF (TG_OP = 'DELETE') THEN
+  return old;
+else
+  new.rev := nextval('revisions_rev_seq'::regclass);
+  new.date := now();
+  return new;
+END IF;
+
+end;$$
+    LANGUAGE plpgsql;
+
+
+--
+-- Name: users_groups_update_ref(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION users_groups_update_ref() RETURNS "trigger"
+    AS $$begin
+
+IF (TG_OP != 'INSERT') then
+update person  set date = now() where person.uid = old.uid;
+update "group" set date = now() where "group".gid = old.gid;
+end if;
+IF (TG_OP != 'DELETE') then
+update person  set date = now() where person.uid = new.uid;
+update "group" set date = now() where "group".gid = new.gid;
+end if;
+
+IF (TG_OP = 'DELETE') then
+  return old;
+ELSE
+  return new;
+END IF;
+
+END;$$
+    LANGUAGE plpgsql;
+
+
+SET default_tablespace = '';
+
+SET default_with_oids = false;
+
+--
+-- Name: revisions; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE revisions (
+    rev integer NOT NULL,
+    date timestamp with time zone DEFAULT now() NOT NULL,
+    "create" timestamp with time zone DEFAULT now() NOT NULL
+);
+
+
+--
+-- Name: revisions_rev_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE revisions_rev_seq
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+--
+-- Name: revisions_rev_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE revisions_rev_seq OWNED BY revisions.rev;
+
+
+--
+-- Name: group; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE "group" (
+    groupname text NOT NULL,
+    gid integer NOT NULL
+)
+INHERITS (revisions);
+
+
+--
+-- Name: person; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE person (
+    name text NOT NULL,
+    firstname text NOT NULL,
+    login text NOT NULL,
+    uid integer NOT NULL,
+    mail text,
+    shell text DEFAULT '/bin/bash'::text,
+    home text,
+    gid integer NOT NULL
+)
+INHERITS (revisions);
+
+
+--
+-- Name: users_groups; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE users_groups (
+    uid integer NOT NULL,
+    gid integer NOT NULL
+)
+INHERITS (revisions);
+
+
+--
+-- Name: rev; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE revisions ALTER COLUMN rev SET DEFAULT nextval('revisions_rev_seq'::regclass);
+
+
+--
+-- Name: goup_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY "group"
+    ADD CONSTRAINT goup_pkey PRIMARY KEY (groupname);
+
+
+--
+-- Name: group_gid_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY "group"
+    ADD CONSTRAINT group_gid_uniq UNIQUE (gid);
+
+
+--
+-- Name: person_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY person
+    ADD CONSTRAINT person_pkey PRIMARY KEY (login);
+
+
+--
+-- Name: person_uid_key; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY person
+    ADD CONSTRAINT person_uid_key UNIQUE (uid);
+
+
+--
+-- Name: users_group_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY users_groups
+    ADD CONSTRAINT users_group_pkey PRIMARY KEY (uid, gid);
+
+
+--
+-- Name: fki_users_group_gid_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_users_group_gid_fkey ON users_groups USING btree (gid);
+
+
+--
+-- Name: group_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER group_rev_tg
+    BEFORE DELETE OR UPDATE ON "group"
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: person_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER person_rev_tg
+    BEFORE DELETE OR UPDATE ON person
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: revisions_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER revisions_rev_tg
+    AFTER DELETE OR UPDATE ON revisions
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: users_groups_update_ref_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER users_groups_update_ref_tg
+    AFTER INSERT OR DELETE OR UPDATE ON users_groups
+    FOR EACH ROW
+    EXECUTE PROCEDURE users_groups_update_ref();
+
+
+--
+-- Name: usres_groups_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER usres_groups_rev_tg
+    BEFORE DELETE OR UPDATE ON users_groups
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: users_group_gid_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY users_groups
+    ADD CONSTRAINT users_group_gid_fkey FOREIGN KEY (gid) REFERENCES "group"(gid);
+
+
+--
+-- Name: users_group_uid_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY users_groups
+    ADD CONSTRAINT users_group_uid_fkey FOREIGN KEY (uid) REFERENCES person(uid);
+
+
+--
+-- Name: public; Type: ACL; Schema: -; Owner: -
+--
+
+REVOKE ALL ON SCHEMA public FROM PUBLIC;
+REVOKE ALL ON SCHEMA public FROM postgres;
+GRANT ALL ON SCHEMA public TO postgres;
+GRANT ALL ON SCHEMA public TO PUBLIC;
+
+
+--
+-- PostgreSQL database dump complete
+--
+
Index: tags/5.2.23/LATMOS-Accounts/bin/la-acls
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-acls	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-acls	(revision 2119)
@@ -0,0 +1,44 @@
+#!/bin/env perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Acls;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-acls - Test acls file
+
+=head1 SYNOPSIS
+
+    la-acls [-d] acl_file
+
+=cut
+
+GetOptions(
+    'd|dump' => \my $dump,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -d: dump the acl list content
+
+=back
+
+=cut
+
+if (!$ARGV[0]) {warn "You must specify 'acl_file', aborting\n"; pod2usage(); }
+
+if(my $acls = LATMOS::Accounts::Acls->new($ARGV[0])) {
+    $acls->dump if ($dump);
+    exit 0;
+} else {
+    warn "Error during acls parsing\n";
+    exit 1;
+}
+
+
Index: tags/5.2.23/LATMOS-Accounts/bin/la-attributes
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-attributes	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-attributes	(revision 2119)
@@ -0,0 +1,120 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-attributes - Show summary of supported objects and attributes of config bases
+
+=head1 SYNOPSIS
+
+List supported objects and attributes
+
+    la-attributes [option]
+    la-attributes [option] object
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my @qbases,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of thr default one.
+
+=item -b|--base basename
+
+Perform query on this base(s) (can be set multiple times).
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+
+if (!@qbases) { @qbases = $LA->list_bases }
+
+if (my ($otype) = @ARGV) {
+    my %supported;
+    my @bases;
+    my %unsupported;
+    foreach my $basename (@qbases) {
+        
+        my $labase = $LA->base($basename) or next;
+        push(@bases, $basename);
+        $labase->is_supported_object($otype) or do {
+            $unsupported{$basename} = 1;
+            next;
+        };
+        foreach($labase->list_canonical_fields($otype, 'a')) {
+            my $attr = $labase->attribute($otype, $_);
+            $supported{$_}{$basename} = sprintf('%s',
+                $attr ? ($attr->ro ? 'r ' : 'rw') : '  '
+            );
+        }
+    }
+
+    if (! keys %supported) {
+        die "No base support this object type $otype\n";
+    }
+
+    my ($objlen) = sort { $b <=> $a } map { length($_) } keys %supported;
+    $objlen+=1;
+    my $header = '|' . (' ' x $objlen) . ' | ' . join(' | ', @bases) . ' |';
+    print '-' x length($header) . "\n";
+    print $header . "\n";
+    print '-' x length($header) . "\n";
+    foreach my $obj (sort keys %supported) {
+        printf("|%${objlen}s |", $obj);
+        foreach (@bases) {
+            print(' '.
+                ($unsupported{$_}
+                    ? 'NA'
+                    : $supported{$obj}{$_} || '  ')
+            );
+            print ' ' x (length($_) - 2) . ' |';
+        }
+        print "\n";
+        print '-' x length($header) . "\n";
+    }
+} else {
+    my %supported;
+    my @bases;
+    foreach my $basename (@qbases) {
+        
+        my $labase = $LA->base($basename) or next;
+        push(@bases, $basename);
+        foreach($labase->list_supported_objects) {
+            $supported{$_}{$basename} = 1;
+        }
+    }
+
+    my ($objlen) = sort { $b <=> $a } map { length($_) } keys %supported;
+    $objlen+=1;
+    my $header = '|' . (' ' x $objlen) . ' | ' . join(' | ', @bases) . ' |';
+    print '-' x length($header) . "\n";
+    print $header . "\n";
+    print '-' x length($header) . "\n";
+    foreach my $obj (sort keys %supported) {
+        printf("|%${objlen}s |", $obj);
+        foreach (@bases) {
+            print($supported{$obj}{$_} ? ' x' : '  ');
+            print ' ' x (length($_) - 1) . ' |';
+        }
+        print "\n";
+        print '-' x length($header) . "\n";
+    }
+}
+
+__END__
Index: tags/5.2.23/LATMOS-Accounts/bin/la-ban-passwd
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-ban-passwd	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-ban-passwd	(revision 2119)
@@ -0,0 +1,91 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-ban-passwd - Deny password for user
+
+=head1 SYNOPSIS
+
+    la-passwd [options] user password
+    la-passwd --current user
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item --current
+
+Ban the current password for this user
+
+=item -l|--lock
+
+Lock the account
+
+=item -e|--encrypted
+
+The password given is already encrypted and must be store without modification
+
+=back
+
+=cut
+
+GetOptions(
+    'c|config=s'  => \my $config,
+    'b|base=s'    => \my $base,
+    'l|lock'      => \my $lock,
+    'e|encrypted' => \my $encrypted,
+    'current'     => \my $current,
+    'help'        => sub { pod2usage(0) },
+) or pod2usage();
+
+my ($user, $password) = @ARGV;
+
+if (!$password && !$current) {
+    warn "You must specify a password or --current\n";
+    pod2usage(1);
+}
+
+my $otype = 'user';
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->wexported(1);
+
+my $obj = $labase->get_object($otype, $user) or do {
+    die "Object $otype $user not found\n";
+};
+
+if ($lock) {
+    $obj->set_c_fields('locked', 1);
+    print "User $user locked\n";
+}
+
+if ($current) {
+    $obj->banCurrentPassword;
+    print "Current password banned\n";
+} else {
+    if ($encrypted) {
+        $password = $labase->passCrypt($password);
+    }
+    $obj->storeBannedPassword($password);
+    print "Given password banned\n";
+}
+
+$labase->commit;
+exit 0;
Index: tags/5.2.23/LATMOS-Accounts/bin/la-cli
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-cli	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-cli	(revision 2119)
@@ -0,0 +1,68 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use LATMOS::Accounts::Cli;
+
+=head1 NAME
+
+    la-cli - A powerfull commandline tool to manage LATMOS::Accounts contents
+
+=head1 SYNOPSIS
+
+    la-cli [options]
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'no-unexp|wo-unexp' => \my $nounexp,
+    'with-unexp'        => \my $unexp,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item --unexp
+
+Take into account all objects (even non propagated ones, with attribute 'exported'=0)
+
+=item --no-unexp
+
+Take into account only propagated objects (attribute 'exported'=1) (default)
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->unexported($nounexp ? 0 : 1);
+
+my $globalenv = LATMOS::Accounts::Cli::globalenv($labase);
+
+if (@ARGV) {
+    $globalenv->run(@ARGV);
+} else {
+    $globalenv->cli;
+}
+print "\n";
+
+exit 0;
+
Index: tags/5.2.23/LATMOS-Accounts/bin/la-config
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-config	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-config	(revision 2119)
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-config - Tools to query configuration of LATMOS::Accounts system.
+
+=head1 SYNOPSIS
+
+List setup base, synchronisation
+
+    la-config [option]
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'ls'         => \my $listsynchros,
+    'base=s'     => \my $base,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Perform query on this base
+
+=item --ls
+
+List synchronisation setup in config
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+
+if ($listsynchros) {
+    my $default_sync = $LA->default_synchro_name || '';
+    print "Syncro:\n";
+    foreach ($LA->list_synchro) {
+        printf("  %s %s (%s => %s)\n",
+            ($_ eq $default_sync ? '*' : ' '),
+            $_,
+            $LA->val("sync:$_", 'from', 'Err: No from'),
+            join(', ', $LA->val("sync:$_", 'to', 'Err: No to')),
+        );
+    }
+} else {
+    my $default = $LA->default_base_name || '';
+    print "Bases:\n";
+    foreach ($LA->list_bases) {
+        printf("  %s %s (%s)\n",
+            ($_ eq $default ? '*' : ' '),
+            $_,
+            $LA->val($_, 'type', 'Err: no type')
+        );
+    }
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-create
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-create	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-create	(revision 2119)
@@ -0,0 +1,147 @@
+#!/bin/env perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use LATMOS::Accounts::Utils;
+
+=head1 NAME
+
+    la-create -- Tools to create object in LATMOS::Account system
+
+=head1 USAGE
+
+    la-create [options] [obj_id]
+
+C<la-create> create objects inside database.
+
+If no input file is given C<$EDITOR> program is launch with an empty template.
+
+The object name must given except when creating C<user> object in SQL database
+for which a try will be done from C<sn> and C<givenName>.
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    'o|object=s' => \my $otype,
+    'f=s'        => \my $inputfile,
+    'ro'         => \my $with_ro,
+    'u|update'   => \my $allow_update,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+$otype ||= 'user';
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -o|object object_type
+
+Query will be performed on this object. Default is the 'User' object.
+
+=item --ro
+
+Include also read-only attributes as comment
+
+=item -u|--update
+
+If the object already exists, then updating it
+
+=item -f FILE
+
+Use this file to get attributes (- use stdin)
+If this option is not present, you'll have a file opened in your prefered editor to edit.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+$labase->wexported(1);
+
+$labase->is_supported_object($otype) or die "$otype object unsupported\n";
+my $objname = $ARGV[0];
+
+sub input_from_handle {
+    my ($fh) = @_;
+    my %attr = LATMOS::Accounts::Utils::parse_obj_file($fh);
+    if ($objname && (my $obj = $labase->get_object($otype, $objname))) {
+        if ($allow_update) {
+            my $res = $obj->set_c_fields(%attr);
+            if ($res) {
+                print "Changes applied\n";
+                $labase->commit;
+                $LA->call_batch_sync;
+            }
+            return $res;
+        } else {
+            die "Object $otype $objname already exists, aborting\n";
+        }
+    } else {
+        if ($objname) {
+            my $res = $labase->create_c_object($otype, $objname, %attr);
+            if($res) {
+                print "Changes applied\n";
+                $labase->commit;
+                $LA->call_batch_sync;
+                return 1;
+            }
+            return 0;
+        } else {
+            my $ochelper = $labase->ochelper($otype);
+
+            my $info = {
+                contents => { %attr },
+            };
+            if ($attr{name}) {
+                $info->{name}{content} = $attr{name};
+            }
+
+            $ochelper->Automate($info) or
+                die "Cannot create object (not enough information ?)\n";
+        } 
+    }
+}
+
+if ($inputfile) {
+    my $handle;
+    if ($inputfile eq '-') {
+        $handle = *STDIN;
+    } else {
+        open($handle, '<', $inputfile) or die
+        "Cannot open input file $@\n";
+    }
+    my $res = input_from_handle($handle);
+    close($handle);
+    exit(!$res);
+} else {
+    exit ! LATMOS::Accounts::Utils::dump_read_temp_file(
+        sub {
+            my ($fh) = @_;
+            $labase->text_empty_dump($fh, $otype,
+                {
+                    only_rw => !$with_ro,
+                }
+            );
+        },
+        sub {
+            my ($fh) = @_;
+            return input_from_handle($fh);
+        }
+    );
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-delete
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-delete	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-delete	(revision 2119)
@@ -0,0 +1,83 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-delete - Tools to delete object in LATMOS-Accounts
+
+=head1 SYNOPSIS
+
+    la-delete [options] obj_id
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    'o|object=s' => \my $otype,
+    'force'      => \my $force,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+$otype ||= 'user';
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -o|object object_type
+
+Query will be performed on this object. Default is the 'User' object.
+
+=item --force
+
+Do not ask confirmation before deleting
+
+=back
+
+=cut
+
+if (!$ARGV[0]) {warn "You must specify 'obj_id', aborting\n"; pod2usage(); }
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+$labase->wexported(1);
+
+if (!$labase->get_object($otype, $ARGV[0])) {
+    die "Cannot find object $otype $ARGV[0]\n";
+}
+
+if (!$force) {
+    my $alea = int(rand(1000));
+    print "Deleting object is definitive !\n";
+    print "Are you sure you want to delete objet $otype $ARGV[0] ?\n";
+    print "To confirm please enter this number: $alea\n";
+    my $enter = <STDIN>;
+    chomp($enter);
+    if (!($enter =~ /^\d+$/ && $enter == $alea)) {
+        die "Wonrg input, aborting\n";
+    }
+}
+
+if ($labase->delete_object($otype, $ARGV[0])) {
+    $labase->commit;
+    print "$otype/$ARGV[0] succefully deleted\n";
+    $LA->call_batch_sync;
+} else {
+    warn "Cannot delete $otype/$ARGV[0]\n";
+    exit(1);
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-edit
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-edit	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-edit	(revision 2119)
@@ -0,0 +1,119 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-edit - Tools to edit object in LATMOS::Accounts system
+
+=head1 SYNOPSIS
+
+    la-edit [options] obj_id
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    'o|object=s' => \my $otype,
+    'e|empty'    => \my $empty_attr,
+    'ro'         => \my $with_ro,
+    'f=s'        => \my $inputfile,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+$otype ||= 'user';
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -o|object object_type
+
+Query will be performed on this object. Default is the 'User' object.
+
+=item -e|--empty
+
+Include also unset attributes
+
+=item --ro
+
+Include also read-only attributes as comment
+
+=item -f FILE
+
+Use this file to get attributes (- use stdin)
+If this option is not present, you'll have a file opened in your prefered editor to edit.
+
+=back
+
+=cut
+
+if (!$ARGV[0]) {warn "You must specify 'obj_id', aborting\n"; pod2usage(); }
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+$labase->wexported(1);
+
+sub input_from_handle {
+    my ($obj, $fh) = @_;
+    my %attr = LATMOS::Accounts::Utils::parse_obj_file($fh);
+    my $res = $obj->set_c_fields(%attr);
+    if($res) {
+        print "Changes applied\n";
+        $labase->commit;
+        $LA->call_batch_sync;
+    }
+    return
+    $res ? 1 : 0;
+}
+
+if (my $ouid = shift(@ARGV)) {
+    my $obj = $labase->get_object($otype, $ouid) or do {
+        die "Object $otype $ouid not found\n";
+    };
+
+    if ($inputfile) {
+        my $handle;
+        if ($inputfile eq '-') {
+            $handle = *STDIN;
+        } else {
+            open($handle, '<', $inputfile) or die
+                "Cannot open input file $@\n";
+        }
+        my $res = input_from_handle($obj, $handle);
+        close($handle);
+        exit(!$res);
+    } else {
+        exit ! LATMOS::Accounts::Utils::dump_read_temp_file(
+            sub {
+                my ($fh) = @_;
+                $obj->text_dump($fh,
+                    {
+                        empty_attr => $empty_attr,
+                        only_rw => !$with_ro,
+                    }
+                );
+            },
+            sub {
+                my ($fh) = @_;
+                return input_from_handle($obj, $fh);
+            }
+        );
+    }
+} else {
+    pod2usage();
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-expired-reminder
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-expired-reminder	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-expired-reminder	(revision 2119)
@@ -0,0 +1,116 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Maintenance;
+use LATMOS::Accounts::Log;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-expired-reminder - Notify manager about expired account needing deletion
+
+=head1 SYNOPSIS
+
+    la-expired-reminder [options]
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+#    'b|base=s'   => \my $base,
+    'm|mail'     => \my $mail,
+    'help'       => sub { pod2usage(0) },
+    'users|u=s'  => \my @users,
+    'to=s'       => \my $to,
+    'at=s'       => \my $delay,
+    'test'       => \my $test,
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -m|mail
+
+Send a mail to user(s) concerned by the future expiration of his/her/their account(s).
+
+=item u|user USER
+
+Send email only to this user if his/her account will expire. Usefull for
+testing and not sending hundred of mails.
+
+Multiple users can be specified by using -u several times
+
+=item --to MAIL
+
+Instead sending email to user(s) and eventually his/her/their manager(s), send the mail to
+only this email address.
+
+=item --at DELAY
+
+Search account expiring in DELAY. Delay must must a number follow by a time
+unit. Example:
+
+    1 month
+    2 days
+
+=item --test
+
+With -m, do not send mail but tell to who the mail would have been sent
+
+=back
+
+=head1 CONFIGURATION
+
+Some variables are taken from configuration directory:
+
+=over 4
+
+=item mailFrom
+
+Specify the From address to use in mail
+
+=item smtp
+
+The smtp server to use to send mail
+
+=item alwayscc
+
+In normal mode (without --to), the mail is also sent this email address.
+
+=back
+
+=head1 USERS EMAIL ADDRESSES
+
+The mail use to send warning to users and manager is taken from 'mail'
+attributes. If the attributes is unset, the user will not receive the mail.
+
+=cut
+
+if (!$test) {
+    la_set_log(
+        syslog => [],
+        console => undef,
+    );
+}
+
+my $LA = LATMOS::Accounts::Maintenance->new($config);
+
+if ($mail) {
+    $LA->expired_account_reminder(
+        to => $to,
+        users => (@users ? \@users : undef),
+        delay => $delay,
+        test => $test,
+    );
+} else {
+    foreach ($LA->find_expired_users($delay)) {
+        printf("%s\n", $_,);
+    }
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-gen-passwd
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-gen-passwd	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-gen-passwd	(revision 2119)
@@ -0,0 +1,102 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use LATMOS::Accounts::Utils;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-gen-passwd - set random password to user.
+
+=head1 SYNOPSIS
+
+    la-gen-passwd [options] userid [userid1 [...]]
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -s|--sync syncname
+
+Use this synchronisation
+
+=item -l length
+
+Generate password of C<length> characters
+
+=item -p
+
+Include non alpha-numeric characters (such as C<#>, C<:>, etc.).
+
+=item --syl
+
+Use syllable instead full random characters
+
+=item -o file
+
+Store the list of username and generated password into F<file>.
+
+=back
+
+=cut
+
+my %gen_options;
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    's|sync=s'   => \my $sync,
+    'p'          => \$gen_options{nonalpha},
+    'syl'        => \$gen_options{syllables},
+    'l=i'        => \$gen_options{length},
+    'o=s'        => \my $output,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+my $otype = 'user';
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $base ? $LA->base($base) : $LA->sync_access($sync);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->wexported(0);
+
+my $handle;
+if ($output) {
+    open($handle, '>', $output) ||
+        die "Cannot open $output file $!\n";
+}
+
+foreach my $user (@ARGV) {
+    my $obj = $labase->get_object($otype, $user) or do {
+        warn "Object $otype $user not found\n";
+        next;
+    };
+
+    my $password = LATMOS::Accounts::Utils::genpassword(
+        %gen_options
+    );
+
+    if ($obj->set_password($password)) {
+        print "$user $password\n";
+        print $handle "$user $password\n" if ($handle);
+        $labase->commit;
+    } else {
+        warn "Error when trying to change password for $user\n";
+    }
+}
+
+close($handle) if ($handle);
+
+exit 0;
Index: tags/5.2.23/LATMOS-Accounts/bin/la-graph.pl
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-graph.pl	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-graph.pl	(revision 2119)
@@ -0,0 +1,100 @@
+#!/bin/env perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use LATMOS::Accounts::Utils;
+use LATMOS::Accounts::Log;
+use GraphViz;
+
+=head1 NAME
+
+    la-group -- Graph user/cells-department
+
+=head1 SYNOPSIS
+
+    la-group [options]
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead the default one
+
+=item -b|--base basename
+
+Query this specific base instead default.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+my $g = GraphViz->new(
+    layout => 'dot',
+    overlap => 'false',
+    rankdir => 1,
+    width => 10, height => 8,
+);
+
+open(my $h, '>', 'graph.png') or die 'cannot open graph';
+
+#foreach ($labase->list_objects('user')) {
+#    $g->add_node($_);
+#}
+
+my %users;
+my %cluster;
+
+sub add_user {
+    my ($user) = @_;
+    $users{$user} and return;
+    my $ou = $labase->get_object('user', $user) or return;
+    my $dpmt = $ou->get_attributes('department');
+    if ($dpmt) {
+        $cluster{$dpmt} ||= {
+            name => $dpmt,
+        };
+    }
+    $g->add_node($user, ($dpmt ? (cluster => $cluster{$dpmt}) : ()));
+    1;
+}
+
+$g->add_node('latmos');
+foreach my $gr ($labase->list_objects('group')) {
+    my $o = $labase->get_object('group', $gr);
+    my $sutype = $o->get_c_field('sutype');
+    ($sutype || '') =~ /^(dpmt|cell)$/ or next;
+    $g->add_node($gr,fillcolor => ($sutype eq 'dpmt' ? 'red' : 'green'), style => 'filled');
+    my $manager = $o->get_attributes('managedBy') || '';
+    warn "MANAGER $manager";
+    if ($manager) {
+        add_user($manager);
+        $g->add_edge('latmos', $gr);
+        $g->add_edge($manager => $gr, weight => 3);
+    }
+    foreach ($o->get_attributes('member')) {
+        $_ eq $manager and next;
+        add_user($_);
+        warn "$_ => $gr";
+        $g->add_edge($gr => $_, weight => 1);
+    }
+}
+
+print $h $g->as_png;
+close($h);
Index: tags/5.2.23/LATMOS-Accounts/bin/la-group
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-group	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-group	(revision 2119)
@@ -0,0 +1,128 @@
+#!/bin/env perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use LATMOS::Accounts::Utils;
+use LATMOS::Accounts::Log;
+
+=head1 NAME
+
+    la-group -- add users to a group
+
+=head1 SYNOPSIS
+
+    la-group [options] [-s|-r] group user1 [user2 [...]]
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    's'                 => \my $set,
+    'r'                 => \my $remove,
+    'no-unexp|wo-unexp' => \my $nounexp,
+    'with-unexp'        => \my $unexp,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+$set && $remove and do {
+    warn "-s and -r cannot be used together\n";
+    pod2usage();
+    exit 1;
+};
+
+my $otype = 'group';
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item --with-unexp
+
+Take into account all objects (even non propagated ones, with attribute
+'exported'=0) (default)
+
+=item --wo-unexp
+
+Take into account only propagated objects (attribute 'exported'=1)
+
+=item -r
+
+Remove the "group" in the user(s) list instead of adding (cannot be used with -s)
+
+=item -s
+
+Set the "group" to user(s) list instead of adding (cannot be used with -r)
+
+=back
+
+=cut
+
+if (@ARGV < 2) {warn "You must specify 'group' and 'user1', aborting\n"; pod2usage(); }
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->unexported($nounexp ? 0 : 1);
+
+$labase->is_supported_object($otype) or die "$otype object unsupported\n";
+my ($objname, @users) = @ARGV;
+
+if (my $obj = $labase->get_object($otype, $objname)) {
+    # getting current value
+    {
+        my @missing = grep {
+            !$labase->get_object('user', $_)
+        } @users;
+        if (@missing) {
+            la_log(LA_ERR, "cannot find user(s) %s", join(', ', sort @missing));
+            exit 1;
+        }
+    }
+    my @current_users = $obj->get_attributes('member');
+    my %uniq_users;
+    if ($set) {
+        foreach (@users) {
+            $uniq_users{$_} = 1;
+        }
+    } else {
+        foreach (@current_users) {
+            $uniq_users{$_} = 1;
+        }
+        if ($remove) {
+            foreach (@users) {
+                delete $uniq_users{$_};
+            }
+        } else {
+            foreach (@users) {
+                $uniq_users{$_} = 1;
+            }
+        }
+    }
+    my @new_users = keys %uniq_users;
+    la_log(LA_INFO, "member was:     %s", join(', ', sort @current_users));
+    if ($obj->set_c_fields(member => [ @new_users ])) {
+        la_log(LA_INFO, "member are now: %s", join(', ', sort @new_users));
+        $labase->commit;
+        exit 0;
+    } else {
+        la_log(LA_INFO, "No change done");
+        exit 1;
+    }
+} else {
+    la_log(LA_ERR, "Cannot find group %s", $objname);
+    exit 1;
+}
+
Index: tags/5.2.23/LATMOS-Accounts/bin/la-guser
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-guser	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-guser	(revision 2119)
@@ -0,0 +1,128 @@
+#!/bin/env perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use LATMOS::Accounts::Utils;
+use LATMOS::Accounts::Log;
+
+=head1 NAME
+
+    la-guser -- add users into groups
+
+=head1 SYNOPSIS
+
+    la-guser [options] [-s|-r] user group1 [group2 [...]]
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    's'                 => \my $set,
+    'r'                 => \my $remove,
+    'no-unexp|wo-unexp' => \my $nounexp,
+    'with-exp'          => \my $unexp,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+$set && $remove and do {                                              
+    warn "-s and -r cannot be used together\n";                       
+    pod2usage();                                                      
+    exit 1;                                                           
+};  
+
+my $otype = 'user';
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item --with-unexp
+
+Take into account all objects (even non propagated ones, with attribute
+'exported'=0) (default)
+
+=item --wo-unexp
+
+Take into account only propagated objects (attribute 'exported'=1)
+
+=item -r
+
+Remove "user" from group(s) instead of adding (cannot be used with -s)
+
+=item -s
+
+Set member list to "user" in group(s) instead of adding (cannot be used with -r)
+
+=back
+
+=cut
+
+if (@ARGV < 2) {warn "You must specify 'user' and 'group1', aborting\n"; pod2usage(); }
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->unexported($nounexp ? 0 : 1);
+
+$labase->is_supported_object($otype) or die "$otype object unsupported\n";
+my ($objname, @groups) = @ARGV;
+
+if (my $obj = $labase->get_object($otype, $objname)) {
+    # getting current value
+    {
+        my @missing = grep {
+            !$labase->get_object('group', $_)
+        } @groups;
+        if (@missing) {
+            la_log(LA_ERR, "cannot find user(s) %s", join(', ', sort @missing));
+            exit 1;
+        }
+    }
+    my @current_groups = $obj->get_attributes('memberOf');
+    my %uniq_groups;
+    if ($set) {
+        foreach (@groups) {
+            $uniq_groups{$_} = 1;
+        }
+    } else {
+        foreach (@current_groups) {
+            $uniq_groups{$_} = 1;
+        }
+        if ($remove) {
+            foreach (@groups) {
+                delete $uniq_groups{$_};
+            }
+        } else {
+            foreach (@groups) {
+                $uniq_groups{$_} = 1;
+            }
+        }
+    }
+    my @new_groups = keys %uniq_groups;
+    la_log(LA_INFO, "memberOf was:     %s", join(', ', sort @current_groups));
+    if ($obj->set_c_fields(memberOf => [ @new_groups ])) {
+        la_log(LA_INFO, "memberOf are now: %s", join(', ', sort @new_groups));
+        $labase->commit;
+        exit 0;
+    } else {
+        la_log(LA_INFO, "No change done");
+        exit 1;
+    }
+} else {
+    la_log(LA_ERR, "Cannot find group %s", $objname);
+    exit 1;
+}
+
Index: tags/5.2.23/LATMOS-Accounts/bin/la-load-csv
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-load-csv	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-load-csv	(revision 2119)
@@ -0,0 +1,91 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use LATMOS::Accounts::Utils;
+use Getopt::Long;
+use Pod::Usage;
+use LATMOS::Accounts::I18N;
+
+=head1 NAME
+
+    la-load-csv - Create object from CSV file
+
+=head1 SYNOPSIS
+
+    la-load-csv Input.csv 
+
+=head1 DESCRIPTION
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'o|object=s'        => \my $otype,
+    't|test'            => \my $test,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+$otype ||= 'user';
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -o|object object_type
+
+Query will be performed on this object. Default is the 'User' object.
+
+=item -t|--test
+
+If the underlying database support it don't commit change at the end (Works
+only on SQL base currently)
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die l("Cannot load base %s\n", $base);
+
+unless ($labase->is_supported_object($otype)) {
+    die "Unsupported object type `$otype'\n";
+}
+
+open(my $fh, '<', $ARGV[0]) or die "Cannot open  $ARGV[0]: $!\n";
+
+loadCSV(
+    $fh,
+    cb => sub {
+        my ($res, $linecount) = @_;
+
+        my $ochelper = $labase->ochelper($otype);
+       
+        my $info = {
+            contents => $res
+        };
+        if ($res->{name}) {
+            $info->{name}{content} = $res->{name};
+        }
+        
+        $ochelper->Automate($info) or
+            die "Cannot create object line $linecount (not enough information ?)\n";
+    },
+);
+
+if($test) {
+    warn "Changes not commited due to test option\n";
+} else {
+    $labase->commit();
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-log-test
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-log-test	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-log-test	(revision 2119)
@@ -0,0 +1,54 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Log;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-log-test - Tools to LATMOS::Account loggin system
+
+=head1 SYNOPSIS
+
+    la-log-test [options]
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'l|level=s'  => \my $level,
+    'mail=s'     => \my $mail,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -l|--level level
+
+Set the log level
+
+=item --mail email
+
+Setup log error sent to email at end of execution
+
+=back
+
+=cut
+
+$level ||= 'LA_INFO';
+
+if ($mail) {
+    la_set_log(mail => $mail);
+}
+
+my $numlevel = eval "LATMOS::Accounts::Log::$level()";
+
+la_log($numlevel, "message from $0 ($level)");
Index: tags/5.2.23/LATMOS-Accounts/bin/la-passwd
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-passwd	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-passwd	(revision 2119)
@@ -0,0 +1,165 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use Term::ReadKey;
+
+=head1 NAME
+
+    la-passwd - set user password                                          
+                                                                            
+=head1 SYNOPSIS                                                            
+                                                                             
+    la-passwd [options] userid                                             
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -s|--sync syncname
+
+Use this synchronisation
+
+=item -t|--test
+
+Don't change the password but check its validity using CrackLib.
+
+=item -f|--force
+
+Change the even it is weak (has no effect with --crypt)
+
+=item --crypt
+
+The given password is crypt using standard unix method and will be injected
+as is inside the base. Works only for base supporting this, and some form of
+password may not be updated.
+
+=item --input FILENAME
+
+Read login/password from a file in form
+
+    username:password
+
+or
+    username:password:...
+
+Everything after the second colon is ignored
+
+If C<FILENAME> is C<->, standard input is used
+
+=back
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    't|test'     => \my $test,
+    's|sync=s'   => \my $sync,
+    'f|force'    => \my $force,
+    'crypt'      => \my $crypt,
+    'i|input=s'  => \my $input,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $base ? $LA->base($base) : $LA->sync_access($sync);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->wexported(1);
+
+sub set_passwd {
+    my ($obj, $password) = @_;
+    my $res = $obj->check_password($password);
+
+    if ($res !~ /^ok$/) {
+        print "Password quality: " . $res . "\n";
+        unless($force) {
+            print "Cannot set bad password, use --force to bypass security\n";
+            return 0;
+        }
+    }
+
+    return 1  if($test);
+
+    if ($obj->set_password($password)) {
+        print "Password succefully changed\n";
+        $labase->commit;
+        return 1;
+    } else {
+        warn "Error when trying to change password\n";
+        return 0;
+    }
+}
+
+if (!$ARGV[0] && !$input) {warn "You must specify 'userid', aborting\n"; pod2usage(); }
+
+my ($username, $password) = @ARGV;
+
+my $otype = 'user';
+
+
+if ($input) {
+    my $handle;
+    if ($input eq '-') {
+        $handle = \*STDIN;
+    } else {
+        open($handle, '<', $input) or die "Cannot open $input: $!\n";
+    }
+
+    while (<$handle>) {
+        chomp();
+        my ($username, $password) = split(/:/);
+        $username or next;
+        $password or next;
+
+        my $obj = $labase->get_object($otype, $username) or do {
+            warn "Object $otype $ARGV[0] not found\n";
+            next;
+        };
+
+        print "Updating user $username\n";
+        if ($crypt) {
+            if ($obj->InjectCryptPasswd($password)) {
+                $labase->commit;
+            }
+        } else {
+            set_passwd($obj, $password);
+        }
+    }
+
+} else {
+    my $obj = $labase->get_object($otype, $username) or do {
+        die "Object $otype $ARGV[0] not found\n";
+    };
+
+    unless($password) {
+        ReadMode('noecho');
+        print "Enter password: ";
+        $password = ReadLine(0);
+        ReadMode 0;
+        print "\n";
+        chomp($password);
+    }
+
+    if ($crypt) {
+        if ($obj->InjectCryptPasswd($password)) {
+            $labase->commit;
+            exit(0);
+        }
+    } else {
+        exit (!set_passwd($obj, $password));
+    } 
+};
+
Index: tags/5.2.23/LATMOS-Accounts/bin/la-qacls
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-qacls	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-qacls	(revision 2119)
@@ -0,0 +1,91 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use Term::ReadKey;
+
+=head1 NAME
+
+    la-qacls - Tools to check ACL
+
+=head1 SYNOPSIS
+
+Show a list of attributes with permission allowed to a given user
+
+    la-qacls [option] [objectname]
+
+If objectname is specified, the acls are checked over this object.
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'o|object=s'        => \my $otype,
+    'u|user=s'          => \my $user,
+    'no-unexp|wo-unexp' => \my $nounexp,
+    'exp'               => \my $unexp,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+$otype ||= 'user';
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item --with-unexp
+
+Take into account all objects (even non propagated ones, with attribute 'exported'=0)
+
+=item --wo-unexp
+
+Take into account only propagated objects (attribute 'exported'=1) (default)
+
+=item -o|object object_type
+
+Query will be performed on this object. Default is the 'User' object.
+
+=item u|user username
+
+Check acls from the point of view of "username". If unset, anonymous is used.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 0);
+
+my $labase = $LA->base($base);
+
+$labase->unexported($unexp ? 1 : 0);
+
+if ($user) {
+    $labase->{_user} = $user;
+}
+
+my $obj = $ARGV[0] ? $labase->get_object($otype, $ARGV[0]) : $otype
+    or die "No object $otype $ARGV[0]";
+
+
+printf(" %s %s\n",
+    ($labase->check_acl($obj, $_, 'w') ? 'w' : ' '),
+    $_,
+) foreach(qw(@CREATE @DELETE));
+
+printf("%s%s %s\n",
+    ($labase->check_acl($obj, $_, 'r') ? 'r' : ' '),
+    ($labase->check_acl($obj, $_, 'w') ? 'w' : ' '),
+    $_,
+) foreach($labase->list_canonical_fields($otype, 'a'));
Index: tags/5.2.23/LATMOS-Accounts/bin/la-query
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-query	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-query	(revision 2119)
@@ -0,0 +1,160 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use LATMOS::Accounts::I18N;
+
+=head1 NAME
+
+    la-query - Tools to query base in LATMOS::Accounts system
+
+=head1 SYNOPSIS
+
+    la-query [options] [obj_id]
+
+=head1 DESCRIPTION
+
+[obj_id] : If present, all set attributes (rw) will be displayed for that obj_id.
+        If none is given, all obj_ids will be printed.
+
+For the default object_type (user), obj_id = login.
+
+Example : la-query lambda
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'o|object=s'        => \my $otype,
+    'e|empty'           => \my $empty_attr,
+    'fmt=s'             => \my $fmt,
+    'filefmt=s'         => \my $filefmt,
+    'ro'                => \my $with_ro,
+    'no-unexp|wo-unexp' => \my $nounexp,
+    'with-unexp'        => \my $unexp,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+$otype ||= 'user';
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -o|object object_type
+
+Query will be performed on this object. Default is the 'User' object.
+
+=item --with-unexp
+
+Take into account all objects (even non propagated ones, with attribute
+'exported'=0) (default)
+
+=item --wo-unexp
+
+Take into account only propagated objects (attribute 'exported'=1)
+
+=item -e|--empty
+
+(Only if obj_id is specified) Include also unset attributes.
+
+=item --ro
+
+(Only if obj_id is specified) Include also read-only attributes as comments.
+
+=item --fmt format
+
+Specify a format of output.
+
+If NOT present and "obj_id" is specified, all non-empty writable attributes will be printed (modified by -e and -ro).
+If NOT present and "obj_id" is NOT specified, only the list of "obj_id" will be printed.
+
+The "format" string may query tags in form:
+
+    %{ATTRIBUTE:printflike}
+
+Where ATTRIBUTE is the name of an attribute and the optional
+C<:printflike> is a printf like string to print the attribute content (without
+the C<%>).
+
+Examples for users:
+
+    --fmt "Name %{sn}, First Name %{givenName}\n"
+
+    --fmt "Name %{sn:20s}, First Name %{givenName}\n"
+
+=item --filefmt format-file
+
+"format-file" is a text file with the same strings as "--fmt" option, WITHOUT quotes (").
+
+It can be on more than one line, but all lines will be concatenated as one.
+Don't forget the "\n" if you really want seperate lines.
+
+Example of format-file:
+
+Name: %{sn}, First Name: %{givenName}\n
+     Department : %{department}\n
+
+Note : --fmt and --filefmt can be used together, and will be concatenated (the "fmt" string first, then what is in the "format-file").
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die l("Cannot load base %s\n", $base);
+
+$labase->unexported($nounexp ? 0 : 1);
+
+unless ($labase->is_supported_object($otype)) {
+    die "Unsupported object type `$otype'\n";
+}
+
+if ($filefmt){
+        open(my $hfmt, '<', $filefmt) or die "Cannot open $filefmt\n";
+        $fmt ||= ''; # avoid undef warning
+        while (<$hfmt>) {
+                chomp($fmt .= $_);
+        }
+        close $hfmt;
+}
+
+if (@ARGV) {
+    foreach my $ouid (@ARGV) {
+        my $obj = $labase->get_object($otype, $ouid) or do {
+            die "Object $otype $ouid not found\n";
+        };
+        if ($fmt) {
+            print $obj->queryformat($fmt);
+        } else {
+            $obj->text_dump(*STDOUT,
+                {
+                    empty_attr => $empty_attr,
+                    only_rw => !$with_ro,
+                }
+            );
+        }
+    }
+} else {
+    foreach (sort $labase->list_objects($otype)) {
+        if ($fmt) {
+            my $o = $labase->get_object($otype, $_) or next;
+            print $o->queryformat($fmt);
+        } else {
+            print "$_\n";
+        }
+    }
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-rename
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-rename	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-rename	(revision 2119)
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use Term::ReadKey;
+
+=head1 NAME
+
+    la-rename - rename object in database
+
+=head1 SYNOPSIS
+
+    la-rename [options] oid newoid
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -s|--sync syncname
+
+Use this synchronisation
+
+=item -o|--otype otype
+
+Modify object having type 'otype', default is user
+
+=back
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    't|test'     => \my $test,
+    's|sync=s'   => \my $sync,
+    'o|otype=s'  => \my $otype,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+if (@ARGV != 2) { pod2usage(); }
+
+$otype ||= 'user';
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $base ? $LA->base($base) : $LA->sync_access($sync);
+$labase && $labase->load or die "Cannot load base\n";
+
+$labase->wexported(1);
+
+my $obj = $labase->get_object($otype, $ARGV[0]) or do {
+    die "Object $otype $ARGV[0] not found\n";
+};
+
+if (!$labase->rename_object($otype, @ARGV)) {
+    die "Error while renaming $otype $ARGV[0] to $ARGV[1]\n";
+}
+
+$labase->commit;
Index: tags/5.2.23/LATMOS-Accounts/bin/la-renewEmp
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-renewEmp	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-renewEmp	(revision 2119)
@@ -0,0 +1,101 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use LATMOS::Accounts::I18N;
+use Date::Parse;
+use DateTime;
+
+=head1 NAME
+
+    la-query - Tools to query base in LATMOS::Accounts system
+
+=head1 SYNOPSIS
+
+    la-query [options] [obj_id]
+
+=head1 DESCRIPTION
+
+[obj_id] : If present, all set attributes (rw) will be displayed for that obj_id.
+        If none is given, all obj_ids will be printed.
+
+For the default object_type (user), obj_id = login.
+
+Example : la-query lambda
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'd=i'               => \my $delay,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+my $otype = 'user';
+$delay ||= 60; # Jours
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -d days
+
+The delay for new employment in days
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die l("Cannot load base %s\n", $base);
+
+$labase->unexported(0);
+
+foreach my $user (@ARGV) {
+    my $ouser = $labase->get_object($otype, $user) or die "Cannot find user $user\n";
+
+    my @employements = $ouser->listEmployment;
+
+    my $lastemp = $employements[0] or do {
+        warn "$user has no employment\n";
+        next;
+    };
+
+    my $oemp = $labase->get_object('employment', $lastemp);
+
+    my $lastday = $oemp->get_attributes('lastday') or do {
+        warn "$user has unlimited employement\n";
+        next;
+    };
+
+    my %newvalues = ( user => $user );
+    foreach (qw(company managerContact contratType department)) {
+        $newvalues{$_} = $oemp->get_attributes($_);
+    }
+    my $time = DateTime->from_epoch(epoch => str2time($lastday));
+    $time->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+    $time->add( days => 1 );
+    $newvalues{firstday} = $time->ymd('-');
+    my $name = "$user-$newvalues{firstday}";
+    $time->add( days => $delay );
+    $newvalues{lastday} = $time->ymd('-');
+
+    $labase->create_object('employment', $name, %newvalues);
+
+    print "$user: new end $newvalues{lastday} ($newvalues{contratType})\n";
+    $labase->commit;
+}
+
Index: tags/5.2.23/LATMOS-Accounts/bin/la-search
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-search	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-search	(revision 2119)
@@ -0,0 +1,111 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-search - Perform object search in Latmos::Accounts base
+
+=head1 SYNOPSIS
+
+    la-search [options] filter [filter2 [...]]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --config configdir
+
+Use this configuration directory instead default
+
+=item --base basename
+
+Perform search in this specific base instead default
+
+=item --with-unexp
+
+Take into account all objects (even non propagated ones, with attribute
+'exported'=0) (default)
+
+=item --wo-unexp
+
+Take into account only propagated objects (attribute 'exported'=1)
+
+=item --fmt format
+
+Specify a format of output.
+
+The "format" string may query tags in form:
+
+    %{ATTRIBUTE:printflike}
+
+Where ATTRIBUTE is the name of an attribute and the optional
+C<:printflike> is a printf like string to print the attribute content
+(without the C<%>).
+
+Examples for users:
+
+    --fmt "Name %{sn}, First Name %{givenName}\n"
+
+    --fmt "Name %{sn:20s}, First Name %{givenName}\n"
+
+=item FILTER
+
+Filter are in form "[ATTRIBUTE][OPERATOR][VALUE]"
+
+where attribute is a supported attribute.
+
+OPERATOR can be:
+
+=over 4
+
+=item C<=>
+
+Search object having attribute strictely equal to VALUE. If VALUE is C<*> match
+any non empty value.
+
+=item C<~>
+
+Search object having attribute containing VALUE.
+
+=back
+
+=back
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'o|object=s'        => \my $otype,
+    'fmt=s'             => \my $fmt,
+    'no-unexp|wo-unexp' => \my $nounexp,
+    'with-unexp'        => \my $unexp,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+$otype ||= 'user';
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->unexported($nounexp ? 0 : 1);
+
+my @result = $labase->search_objects($otype, @ARGV);
+foreach (@result) {
+    if ($fmt) {
+        my $o = $labase->get_object($otype, $_);
+        print $o->queryformat($fmt);
+    } else {
+        print "$_\n";
+    }
+}
+
+STDOUT->autoflush(1);
+warn "\n" . scalar(@result) . " results\n";
+exit(0);
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-alias
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-alias	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-alias	(revision 2119)
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sql-alias - Tools to create or edit object alias
+
+=head1 SYNOPSIS
+
+    la-sql-properties [options] obj_id
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    'o|object=s' => \my $otype,
+    'r|remove'   => \my $remove,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+$otype ||= 'user';
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -o|object object_type
+
+Query will be performed on this object. Default is the 'User' object.
+
+=item -r|remove
+
+Remove the alias given in argument
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+$labase->wexported(1);
+
+my ($name, $for) = @ARGV;
+
+if (!$name) {
+    foreach my $id ($labase->search_objects($otype, 'oalias=*')) {
+        my $isfor = '';
+        if (my $obj = $labase->GetAlias($otype, $id)) {
+            $isfor = $obj->get_attributes('oalias');
+        }
+        print "$id -> $isfor\n";
+    }
+    exit 0;
+}
+
+if ($remove) {
+    if($labase->RemoveAlias($otype, $name)) {
+        print "Alias $otype/$name removed\n";
+        exit(0);
+    } else {
+        die "Error while remove alias $otype/$name";
+    }
+}
+
+$for or do { pod2usage(1); };
+
+if (my $obj = $labase->GetAlias($otype, $name)) {
+    if ($obj->set_c_fields(oalias => $for)) {
+        $labase->commit;
+        print "Alias $name updated\n";
+    } else {
+        die "Alias $name unchanged\n";
+    }
+} else {
+    if ($labase->CreateAlias($otype, $name, $for)) {
+        $labase->commit;
+        print "Alias $name created\n";
+    } else {
+        die "Alias $name not created\n";
+    }
+}
+
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-attrvalues
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-attrvalues	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-attrvalues	(revision 2119)
@@ -0,0 +1,75 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use Term::ReadKey;
+
+=head1 NAME
+
+    la-sql-attrvalue - Set limited choice to attributes
+
+=head1 SYNOPSIS
+
+    la-rename [options] attribute [value]
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -o|--otype otype
+
+Modify object having type 'otype', default is user
+
+=item -r|--remove
+
+Remove the given value (or all if no value are given)
+
+=back
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    't|test'     => \my $test,
+    'o|otype=s'  => \my $otype,
+    'r|remove'   => \my $remove,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+my ($attribute, @values) = @ARGV;
+
+if (!$attribute) { pod2usage(); }
+
+$otype ||= 'user';
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base\n";
+
+if ($remove) {
+    $labase->DelAttrValue($otype, $attribute, @values) or die "Cannot remove this value";
+} else {
+    if (@values) {
+        $labase->AddAttrValue($otype, $attribute, @values) or die "Cannot add this value";
+    }
+
+    print "Otype: $otype, Attribute: $attribute\n";
+    foreach ($labase->ListAttrValue($otype, $attribute)) {
+        print "\t$_\n";
+    }
+}
+
+
+$labase->commit;
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-crypt-passwd
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-crypt-passwd	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-crypt-passwd	(revision 2119)
@@ -0,0 +1,200 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use Term::ReadKey;
+use Crypt::RSA;
+
+=head1 NAME
+
+    la-crypt-passwd - Tools to managed rsa crypted password in LATMOS Account system
+
+=head1 SYNOPSIS
+
+    la-crypt-passwd [options] [--genkey|--regen] [--set BASE]
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'help'       => sub { pod2usage(0) },
+    'genkey'     => \my $genkey,
+    'regen'      => \my $regen,
+    'delkey'     => \my $delkey,
+    'set=s'      => \my $set,
+    'base=s'     => \my $base,
+    'u|user=s'   => \my @users,
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item --genkey
+
+Generate a RSA key and store it into database
+
+If one is already present, use regen to force generation of a new one
+
+=item --regen
+
+Like --genkey but a new key will replace the current one if already present.
+Stored password will be read and encrypted again using the new key.
+
+=item --delkey
+
+Delete the current peer key and all encrypted password stored.
+
+=item --base base
+
+Work on this specific base instead default one
+
+=item --set BASE
+
+Read password from database, decrypt it and then set it in BASE given as
+argument.
+
+=item -u|--user USER
+
+Set password only for this user (can be set multiple times).
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+$labase->wexported(1);
+
+my $clear;
+
+sub get_clear_password {
+
+    my @users_to_decode = @_;
+
+    $clear and return $clear;
+    my %encpasswd = $labase->get_rsa_password;
+    scalar(keys %encpasswd) or return {};
+    ReadMode('noecho');
+    print "Enter password for current passphrase: ";
+    my $password = ReadLine(0);
+    ReadMode 0;
+    print "\n";
+    my $private_key = $labase->private_key($password) or
+        die "Cannot get private key\n";
+    my $rsa = new Crypt::RSA ES => 'PKCS1v15';
+
+    my %clear_passwd;
+
+    if (!@users_to_decode) {
+        @users_to_decode = sort keys %encpasswd;
+    }
+
+    printf
+        "Trying to get current stored password (%d)\n",
+        scalar(@users_to_decode);
+
+    foreach (@users_to_decode) {
+        my $clearp = $rsa->decrypt (
+                Cyphertext => $encpasswd{$_},
+                Key        => $private_key,
+                Armour     => 1,
+        );
+        if (defined $clearp) {
+            $clear_passwd{$_} = $clearp;
+        } else {
+            die "Cannot get password for $_, crypt module said :" . $rsa->errstr() .
+                "Was the password correct ?\n";
+        }
+    }
+    return \%clear_passwd;
+}
+
+if ($set) {
+    if (!$labase->get_global_value('rsa_private_key')) {
+        warn "No rsa key found in database\n";
+    }
+    my $destbase = $LA->base($set) or die "Cannot get base $set\n";
+    my $clearpasswd = get_clear_password(@users);
+
+    my @userstoset = @users ? @users : keys %$clearpasswd;
+
+    foreach (@userstoset) {
+        $clearpasswd->{$_} or next;
+        my $obj = $destbase->get_object('user', $_) or do {
+            warn "Cannot find user $_ in destination base, need sync ?\n";
+            next;
+        };
+        $obj->set_password($clearpasswd->{$_}) and
+            print "Password set for $_\n";
+    }
+    $destbase->commit;
+} elsif ($regen || $genkey) {
+    if ($labase->get_global_value('rsa_private_key') && !$regen) {
+        die <<EOF;
+A rsa key were found in database please use --regen to force a new key
+generation. Notice this will force decrypt current stored password to encrypt
+them again
+EOF
+    }
+
+    my $clearpasswd = get_clear_password();
+
+    my $password;
+    while (1) {
+        ReadMode('noecho');
+        print "Enter password for new key: ";
+        $password = ReadLine(0);
+        print "\n";
+        print "Enter password again for new key: ";
+        my $password2 = ReadLine(0);
+        ReadMode 0;
+        print "\n";
+        if ($password eq $password2) {
+            last;
+        } else {
+            print "Password mismatch, try again:\n";
+        }
+    }
+
+    print "Generating new RSA key...\n";
+    my ($public, $private) = $labase->generate_rsa_key($password);
+
+    $labase->store_rsa_key($public, $private);
+    foreach (keys %$clearpasswd) {
+        my $obj = $labase->get_object('user', $_);
+        $obj->setCryptPassword($clearpasswd->{$_});
+    }
+    $labase->commit;
+} elsif ($delkey) {
+    if (! $labase->get_global_value('rsa_public_key')) {
+        die "There is no key in this base, not deleting nothing\n";
+    }
+    my %encpasswd = $labase->get_rsa_password;
+    print "Deleting password...\n";
+    foreach my $user (keys %encpasswd) {
+        my $ouser = $labase->get_object('user', $user) or next;
+        $ouser->set_c_fields('encryptedPassword' => undef) or
+            die "Cannot delete encryptedPassword attribute for $user\n";
+    }
+    $labase->set_global_value('rsa_public_key', undef);
+    $labase->set_global_value('rsa_private_key', undef);
+    $labase->commit;
+} else {
+    if ($labase->get_global_value('rsa_private_key')) {
+        my $clearpasswd = get_clear_password();
+        foreach (keys %$clearpasswd) {
+            printf("%s: %s\n", $_, $clearpasswd->{$_});
+        }
+    } else {
+        warn "No rsa key found in database\n";
+    }
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-edit-form
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-edit-form	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-edit-form	(revision 2119)
@@ -0,0 +1,167 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sql-edit-form - Tools to edit forms (accreq)
+
+=head1 SYNOPSIS
+
+    la-sql-edit-form formid
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    'f=s'        => \my $inputfile,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -f FILE
+
+Use this file to get attributes (- use stdin)
+If this option is not present, you'll have a file opened in your prefered editor to edit.
+
+=back
+
+=cut
+
+if (!$ARGV[0]) { warn "You must specify 'obj_id', aborting\n"; pod2usage(); }
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+$labase->wexported(1);
+
+sub input_from_handle {
+    my ($obj, $fh) = @_;
+    my $yaml = join('', <$fh>);
+    my $res = $obj->set_c_fields(form => $yaml);
+    if($res) {
+        print "Changes applied\n";
+        $labase->commit;
+        $LA->call_batch_sync;
+    }
+    return
+    $res ? 1 : 0;
+}
+
+if (my $ouid = shift(@ARGV)) {
+    my $obj = $labase->get_object('accreq', $ouid) or do {
+        die "Object accreq $ouid not found\n";
+    };
+
+    if ($inputfile) {
+        my $handle;
+        if ($inputfile eq '-') {
+            $handle = *STDIN;
+        } else {
+            open($handle, '<', $inputfile) or die
+                "Cannot open input file $@\n";
+        }
+        my $res = input_from_handle($obj, $handle);
+        close($handle);
+        exit(!$res);
+    } else {
+        exit ! LATMOS::Accounts::Utils::dump_read_temp_file(
+            sub {
+                my ($fh) = @_;
+                print $fh $obj->get_attributes('form');
+            },
+            sub {
+                my ($fh) = @_;
+                return input_from_handle($obj, $fh);
+            }
+        );
+    }
+} else {
+    pod2usage();
+}
+
+=head1 FORM FORMAT
+
+=head2 EXAMPLE
+
+    ---
+    attrs:
+      - formtype: textarea
+        label: Please explain why your name must changed
+        name: reason
+      - sn
+      - givenName
+    description: Change my name
+    notifyMail: sysadmin@nomail
+
+=head2 SYNTAX
+
+The form must be describe in YAML format (L<http://en.wikipedia.org/wiki/YAML>).
+
+=head2 OPTIONS
+
+=over 4
+
+=item description
+
+A short description about this form
+
+=item notifyMail
+
+If set a mail is sent to this address when someone submit the form
+
+=item attrs
+
+The list of attributes the form must contains. See below.
+
+=back
+
+=head2 ATTRIBUTES DESCRIPTION
+
+Each attributes is either:
+
+=over 4
+
+=item a name
+
+Just the name of an object's attribute the is related (C<sn>, C<givenName> for user objects)
+
+=item an option list
+
+A list of options describing how the attribute must appear:
+
+=over 4
+
+=item name
+
+The internal short name of attribute
+
+=item label
+
+The text displayed to user
+
+=item formtype
+
+The type of field to diaply (text, textarea, date, checkbox or list)
+
+=back
+
+=back
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-exchange-hostname
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-exchange-hostname	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-exchange-hostname	(revision 2119)
@@ -0,0 +1,66 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sql-exchange-ip - Exchange name between two host
+
+=head1 SYNOPSIS
+
+    la-exchange-ip from to
+
+Example : la-sql-exchange-hostname foo bar
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->unexported(1);
+
+my ($from, $to) = @ARGV or pod2usage();
+
+$labase->get_object('nethost', $from) or die "Cannot find host $from";
+$labase->get_object('nethost', $to) or die "Cannot find host $to";
+
+my $tempname;
+while (1) {
+    $tempname = $from . '-' . join('', map { ('a' .. 'z')[rand(26)] } (0 .. 7));
+    $labase->get_object('nethost', $tempname) or last;
+}
+
+$labase->rename_object('nethost', $from, $tempname) or die "Cannot rename $from to a temporary name\n";
+$labase->rename_object('nethost', $to, $from) or die "Cannot rename $to to $from\n";
+$labase->rename_object('nethost', $tempname, $to) or die "Cannot rename $from to a its final name $to\n";
+
+$labase->commit;
+print "Exchange between $from and $to done\n";
+
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-exchange-ip
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-exchange-ip	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-exchange-ip	(revision 2119)
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sql-exchange-ip - Exchange IP between two host
+
+=head1 SYNOPSIS
+
+    la-sql-exchange-ip ip1 ip2
+
+Example : la-sql-exchange-ip 172.1.2.3 192.168.3.2
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->unexported(1);
+
+my ($from, $to) = @ARGV or pod2usage();
+
+if ($labase->nethost_exchange_ip($from, $to)) {
+    $labase->commit;
+    print "IP succesfully exchange between $from and $to\n";
+    exit(0);
+} else {
+    warn "Error while exchanging ip\n";
+    exit(1);
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-find-expired
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-find-expired	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-find-expired	(revision 2119)
@@ -0,0 +1,54 @@
+#!/usr/bin/perl
+
+# $Id: la-find-expired 2795 2010-07-31 10:04:13Z nanardon $
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Maintenance;
+use LATMOS::Accounts::Log;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sql-find-expired - Find expired user
+
+=head1 SYNOPSIS
+
+    la-sql-find-expired [options]
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'help'       => sub { pod2usage(0) },
+    'old=s'       => \my $delay,
+    'test'       => \my $test,
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item old DELAY
+
+The minimum delay user has been expired
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts::Maintenance->new($config);
+
+foreach ($LA->find_expired_users($delay)) {
+    my $obj = $LA->_base->get_object('user', $_);
+    printf("%s - %s (%s%s)\n", $obj->id, $obj->get_attributes('displayName'),
+        $obj->get_attributes('expireText'),
+        ($obj->get_attributes('manager') ? ' ' .
+            $obj->get_attributes('manager') : ''),
+    );
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-freeip
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-freeip	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-freeip	(revision 2119)
@@ -0,0 +1,74 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-freeip - Search unused ip in database
+
+=head1 SYNOPSIS
+
+    la-buildnet [-f|-r] zone [...]
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'help'              => sub { pod2usage(0) },
+    'n'                 => \my $network,
+    'f|first'           => \my $first,
+    'r|random'          => \my $random,
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -f|--first
+
+Return the first free ip
+
+=item -r|--random
+
+Return a random free ip in the unallocated pool
+
+=back
+
+=cut
+
+@ARGV or pod2usage(1);
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+my @ips;
+foreach (@ARGV) {
+    my $ozone = $labase->get_object('netzone', $_) or die "Can't find zone $_";
+    push(@ips, grep { $_ } $ozone->get_attributes('freeIP'));
+}
+if (@ips) {
+    if ($first) {
+        print "$ips[0]\n";
+    } elsif ($random) {
+        print $ips[rand($#ips)] . "\n";
+
+    } else {
+        print join('', map { "$_\n" } @ips);
+    }
+} else {
+    warn "No free ip found\n";
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-log
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-log	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-log	(revision 2119)
@@ -0,0 +1,67 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sql-log - Dislay objects log
+
+=head1 SYNOPSIS
+
+    la-edit [options] [obj_id]
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    'o|object=s' => \my $otype,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+$otype ||= 'user';
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -o|object object_type
+
+Query will be performed on this object. Default is the 'User' object.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+$labase->wexported(1);
+
+my @logs = $ARGV[0] 
+    ? $labase->getobjectlogs($otype, $ARGV[0])
+    : $labase->getlogs();
+
+foreach(@logs) {
+    printf("%s (%d), %s: %s/%s (%d) %s\n",
+        $_->{logdate},
+        $_->{irev} || -1,
+        $_->{username},
+        $_->{otype},
+        $_->{name},
+        $_->{ikey},
+        $_->{message},
+    );
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-properties
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-properties	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-properties	(revision 2119)
@@ -0,0 +1,81 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sql-properties - Tools to edit object internals SQL base properties
+
+=head1 SYNOPSIS
+
+    la-sql-properties [options] obj_id
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    'o|object=s' => \my $otype,
+    'nodelete=s' => \my $nodelete,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+$otype ||= 'user';
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -o|object object_type
+
+Query will be performed on this object. Default is the 'User' object.
+
+=item --nodelete on|off
+
+Turn attribute C<nodelete> to C<on> or C<off>
+
+=back
+
+=cut
+
+if (!$ARGV[0]) { warn "You must specify 'obj_id', aborting\n"; pod2usage(); }
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+$labase->wexported(1);
+
+my $ouid = shift(@ARGV) or do { pod2usage(1); };
+
+my $obj = $labase->GetRawObject($otype, $ouid) or do {
+    die "Object $otype $ouid not found\n";
+};
+
+sub checkonoff {
+    my ($value, $step) = @_;
+    if (! $value =~ /^(on|off)$/) {
+        warn "Value for --$step must be on or off\n";
+        pod2usage(1);
+    }
+    return $value eq 'on' ? 1 : 0;
+}
+
+if (defined($nodelete)) {
+    my $v = checkonoff($nodelete, 'nodelete');
+    $obj->SetNoDelete($v);
+}
+
+print 'internal: ' . ($obj->get_field('internobject') ? 'yes' : 'no') . "\n";
+print 'nodelete: ' . ($obj->get_field('nodelete') ? 'yes' : 'no') . "\n";
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-recompute
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-recompute	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-recompute	(revision 2119)
@@ -0,0 +1,68 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sql-recompute - Update computed attribute
+
+=head1 SYNOPSIS
+
+    la-sql-upgrade [options]
+
+=cut
+
+GetOptions(
+    'c|config=s'     => \my $config,
+    'b|base=s'       => \my $base,
+    'no-commit'      => \my $nocommit,
+    'v|verbose'      => \my $verbose,
+    'h|help'         => sub { pod2usage(1); },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=back
+
+=cut
+
+$| = 1; # autoflush
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase->wexported(1);
+
+foreach my $user ($labase->list_objects('user')) {
+    print "Updating user $user\n";
+    my $ouser = $labase->get_object('user', $user) or next;
+    $ouser->computeEmploymentDate();
+}
+
+foreach my $group ($labase->search_objects('group', 'sutype=dpmt')) {
+    print "Updating group $group\n";
+    my $ogroup = $labase->get_object('group', $group) or next;
+    $ogroup->_update_employment_manager;
+}
+
+$labase->PopulateDynData;
+
+if ($nocommit) {
+    $labase->rollback;
+} else {
+    $labase->commit;
+}
+print "Process terminated successfully\n";
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-rename-host
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-rename-host	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-rename-host	(revision 2119)
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sql-rename-host - rename an network host
+
+=head1 SYNOPSIS
+
+    la-sql-rename-host [--no-cname] from to
+
+Example : la-rename-host foo bar
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'no-cname'          => \my $nocname,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item --no-cname
+
+By default the old name is added as CNAME for compatibility, this option don't
+add previous name as CNAME.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->unexported(1);
+
+my ($from, $to) = @ARGV or pod2usage();
+
+if ($labase->rename_nethost($from, $to, addcname => (!$nocname))) {
+    $labase->commit;
+    print "Host succefully rename from $from to $to\n";
+    exit(0);
+} else {
+    warn "Error while renaming $from\n";
+    exit(1);
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-reset-passwd
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-reset-passwd	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-reset-passwd	(revision 2119)
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use Term::ReadKey;
+use Crypt::RSA;
+
+=head1 NAME
+
+    la-crypt-passwd - Tools to managed rsa crypted password in LATMOS Account system
+
+=head1 SYNOPSIS
+
+    la-crypt-passwd [options] username
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'help'       => sub { pod2usage(0) },
+    'base=s'     => \my $base,
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+$labase->wexported(1);
+
+my ($username, $url) = @ARGV;
+
+my $user = $labase->get_object('user', $username);
+
+$user->SendPasswordReset($url);
+$user->base->commit;
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-rev
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-rev	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-rev	(revision 2119)
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-rev - Show current base revision
+
+=head1 SYNOPSIS
+
+    la-rev
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    'help'       => sub { pod2usage(0); },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+print $labase->current_rev . "\n";
+
+exit(0);
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-runstat
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-runstat	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-runstat	(revision 2119)
@@ -0,0 +1,66 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Maintenance;
+use LATMOS::Accounts::Log;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sql-runstat - Collect statistic data
+
+=head1 SYNOPSIS
+
+    la-sql-runstat [options] statname
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    's|store'    => \my $store,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base base
+
+Use this specific base
+
+=item -s
+
+Run the statistics and store data into history.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+$labase->wexported(1);
+
+my ($name) = @ARGV;
+
+my $stat = $labase->get_object('stat', $name) or die "no stat name $name";
+
+my $res;
+if ($store) {
+    $stat->collect and $labase->commit;
+    $res = $stat->getStat;
+} else {
+    $res = $stat->compute;
+}
+
+foreach (sort { $res->{$b} <=> $res->{$a} } keys %$res) {
+   print "$_: " . $res->{$_} . "\n";
+} 
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-stat2csv
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-stat2csv	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-stat2csv	(revision 2119)
@@ -0,0 +1,61 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Maintenance;
+use LATMOS::Accounts::Log;
+use Getopt::Long;
+use Pod::Usage;
+use Text::CSV;
+
+=head1 NAME
+
+    la-sql-stat2csv - Output collected statistics data
+
+=head1 SYNOPSIS
+
+    la-expired-reminder [options] statname
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+    'b|base=s'   => \my $base,
+    'help'       => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base base
+
+Use this specific base
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+$labase->wexported(1);
+
+my ($name) = @ARGV;
+
+my $stat = $labase->get_object('stat', $name) or die "no stat name $name";
+
+my $data = $stat->getAllStat();
+my $csv = Text::CSV->new({eol => $/});
+
+foreach my $date (sort keys %$data) {
+    foreach my $val (sort keys %{ $data->{$date} }) {
+        $csv->print(\*STDOUT, [ $date, $val, $data->{$date}{$val} ]);
+    }
+}
+
+exit 0
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-switch-cname
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-switch-cname	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-switch-cname	(revision 2119)
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sql-switch-cname - Exchange (c)name between two host
+
+=head1 SYNOPSIS
+
+    la-exchange-ip cname [cname] [-a name] desthost
+
+Example : la-rename-host foo bar
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'cname|cn=s'        => \my @cnames,
+    'a|arecord=s'       => \my @arecords,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->unexported(1);
+
+if (!@ARGV) {
+    pod2usage(1);
+}
+
+my ($to, $from) = ($ARGV[-1], $ARGV[-2]);
+
+my $oto = $labase->get_object('nethost', $to) or die "Cannot find $to\n";
+
+foreach my $cname (@cnames) {
+    my @listhost = $labase->search_objects('nethost', "cname=$cname");
+    my ($nhost, @others) = $from
+        ? grep { $_ eq $from } @listhost
+        : @listhost;
+    if (@others) {
+        die "Multiple hosts found for $cname: " . join(', ', $nhost, @others) . "\n";
+    } elsif (!$nhost) {
+        die "No cname $cname found\n";
+    } elsif ($nhost eq $to) {
+        die "Source and destination are the same host for cname $cname\n";
+    }
+    my $onhost = $labase->get_object('nethost', $nhost);
+    warn "CNAME $cname: $nhost => $to\n";
+
+    $onhost->delAttributeValue('cname', $cname) or die "Cannot remove $cname from $nhost\n";
+    $oto->addAttributeValue   ('cname', $cname) or die "Cannot add $cname to $to\n";
+}
+
+foreach my $aname (@arecords) {
+    my @listhost = $labase->search_objects('nethost', "otherName=$aname");
+    my ($nhost, @others) = $from
+        ? grep { $_ eq $from } @listhost
+        : @listhost;
+    if (@others) {
+        die "Multiple hosts found for $aname: " . join(', ', $nhost, @others) . "\n";
+    } elsif (!$nhost) {
+        die "No otherName $aname found\n";
+    } elsif ($nhost eq $to) {
+        die "Source and destination are the same host for a $aname\n";
+    }
+    my $onhost = $labase->get_object('nethost', $nhost);
+    warn "CNAME $aname: $nhost => $to";
+
+    $onhost->delAttributeValue('otherName', $aname) or die "Cannot remove $aname from $nhost\n";
+    $oto->addAttributeValue   ('otherName', $aname) or die "Cannot add $aname to $to\n";
+}
+
+$labase->commit;
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-updsshfp
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-updsshfp	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-updsshfp	(revision 2119)
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sql-updsshfp - Update ssh finger print from ssh-keygen output
+
+=head1 SYNOPSIS
+
+    la-sql-updsshfp host
+
+    la-sql-updsshfp host -f file
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'f|file=s'          => \my $file,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=item -f|--file file
+
+Specify the file containing the ssh-keygen output. By default data are read from
+standard input.
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->unexported(1);
+
+my $host = $ARGV[0];
+
+my $object = $labase->get_object('nethost', $host) or die 
+    "Can't find nethost $host\n";
+
+my $text = '';
+while (my $line = <STDIN>) {
+    $text .= $line;
+}
+
+$object->set_fields('sshfpUpdate', $text) or die "Can update base\n";
+
+$labase->commit;
+
+print "Done\n";
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sql-upgrade
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sql-upgrade	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sql-upgrade	(revision 2119)
@@ -0,0 +1,60 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+
+my $DATADIR = '@DATADIR@';
+
+=head1 NAME
+
+    la-sql-upgrade - Update SQL base schema
+
+=head1 SYNOPSIS
+
+    la-sql-upgrade [options] [name]
+
+=cut
+
+GetOptions(
+    'c|config=s'     => \my $config,
+    'b|base=s'       => \my $base,
+    'no-commit'      => \my $nocommit,
+    'v|verbose'      => \my $verbose,
+    'h|help'         => sub { pod2usage(1); },
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -b|--base basename
+
+Query this specific base instead of the default one.
+
+=back
+
+=cut
+
+$| = 1; # autoflush
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase->unexported(1);
+
+require LATMOS::Accounts::Bases::Sql::upgrade;
+
+$labase->SchemaUpgrade() or die "An error occur";
+
+if ($nocommit) {
+    $labase->rollback;
+} else {
+    $labase->commit;
+}
+print "Process terminated successfully\n";
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sync
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sync	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sync	(revision 2119)
@@ -0,0 +1,123 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use LATMOS::Accounts::Synchro;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-sync - Tools to synchronize base
+
+=head1 SYNOPSIS
+
+Show configured bases:
+
+    la-sync [options]
+    la-sync --from source --to dest -o type [-a|obj1 obj2]
+
+=cut
+
+GetOptions(
+    'c|config=s'   => \my $config,
+    'help'         => sub { pod2usage(0) },
+    'from=s'       => \my $from,
+    'to=s'         => \my @to,
+    'nocreate'     => \my $nocreate,
+    'nodelete'     => \my $nodelete,
+    'test'         => \my $test,
+    'o|object=s'   => \my $otype,
+    's|syncname=s' => \my $syncname,
+    'b|batch'      => \my $batch,
+    'unexp'        => \my $unexp,
+    'a|all'        => \my $all
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item --from basename
+
+Specify source base to use
+
+=item --to basename
+
+Specify destination base to use, this option can be used
+severals times
+
+=item --nodelete
+
+Don't delete object on destination
+
+=item --nocreate
+
+Don't create object, synchronize existing
+
+=item --test
+
+Don't change anything, just test
+
+=item -s|--syncname name
+
+Use synchronisation named C<name> in configuration instead default
+
+=item -o|object object_type
+
+Query will be performed on this object. Default is the 'User' object.
+
+=item -b|--batch
+
+Don't really perform any synchronisation but try to call daemon process
+to perform immediate synchronisation. 
+
+=item --unexp
+
+Sync unexported objects in source base
+
+=back
+
+=cut
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+
+if ($batch) {
+    warn "Using la-sync -b is deprecated, use la-sync-ctl -s instead\n";
+    exit ($LA->call_batch_sync ? 0 : 1);
+}
+
+my $sync = $LA->create_synchro(
+    $syncname,
+    from => $from,
+    to => (@to ? [ @to ] : undef),
+    test     => $test,
+    unexported => $unexp,
+) or die "cannot create sync object\n";
+$sync->load_dest and return;
+if ($otype) {
+    my @list = $all
+        ? $sync->from->list_objects($otype)
+        : @ARGV;
+
+    foreach (@list) {
+        warn "Try to sync $otype: $_\n";
+        $sync->sync_object($otype, $_, onepass => 1) or do {
+	    warn "Error while synching $_ ($otype)\n";
+	    exit 1;
+        }
+    }
+} else {
+    $sync->process(
+    	test     => $test,
+    	nodelete => $nodelete,
+    	nocreate => $nocreate,
+    ) or warn "Sync has failed\n";
+}
+
+exit 0;
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sync-ctl
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sync-ctl	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sync-ctl	(revision 2119)
@@ -0,0 +1,94 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Getopt::Long;
+use Pod::Usage;
+use Config::IniFiles;
+use LATMOS::Accounts;
+use LATMOS::Accounts::SyncManager;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Task;
+
+=head1 NAME
+
+    la-sync-manager - Daemon performing synchronisation and various tasks
+
+=head1 SYNOPSIS
+
+    la-sync-manager [options...]
+
+=cut
+
+GetOptions(
+    'c|config=s'   => \my $config,
+    'help'         => sub { pod2usage(0) },
+    'r|reset=s'    => \my $reset,
+    's|sync'       => \my $sync,
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration instead default one
+
+=item --help
+
+Display this help
+
+=item --reset|-r module
+
+Reset the check point to force the module to be rerun even no change occurs in
+database.
+
+If the given module name is C<all>, all module get reset.
+
+=item --sync|-s
+
+Tell to L<la-sync-manager> daemon to immediatly perform a synchronisation.
+
+=back
+
+=cut
+
+my $exit = 0;
+
+if ($reset) {
+    my $syncm = LATMOS::Accounts::SyncManager->new($config) or do {
+        la_log LA_ERR, "Cannot instanciate Sync Manager";
+        exit(1);
+    };
+    foreach ($reset eq 'all' ? ($syncm->list_modules) : ($reset)) {
+        $syncm->reset_module_checkpoint($_) or $exit=1;
+    }
+}
+
+if ($sync) {
+    my $LA = LATMOS::Accounts->new($config, noacl => 1);
+    $LA->call_batch_sync or $exit=1;
+}
+
+exit($exit);
+
+__END__
+
+=head1 SEE ALSO
+
+L<la-sync-manager.ini>, L<latmos-accounts.ini>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sync-manager
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sync-manager	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sync-manager	(revision 2119)
@@ -0,0 +1,243 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Getopt::Long;
+use Pod::Usage;
+use Config::IniFiles;
+use LATMOS::Accounts;
+use LATMOS::Accounts::SyncManager;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Task;
+
+=head1 NAME
+
+    la-sync-manager - Daemon performing synchronisation and various tasks
+
+=head1 SYNOPSIS
+
+    la-sync-manager [options...]
+
+=cut
+
+GetOptions(
+    'nodaemon' => \my $nodaemon,
+    'c|config=s'   => \my $config,
+    'help'         => sub { pod2usage(0) },
+    'wait=i'       => \my $wait,
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item --nodaemon
+
+Don't go into background
+
+=item -c|--config configdir
+
+Use this configuration instead default one
+
+=item --help
+
+Display this help
+
+=item --wait minutes
+
+Wait this number of minutes before process synchronisation (default is 5
+minutes)
+
+=back
+
+=head1 CONFIUGRATION
+
+=head2 CONFIGURATION FILE
+
+See L<la-sync-manager.ini>
+
+=head2 AVAILABLE TASK MODULE
+
+=head3 L<LATMOS::Accounts::Task::Basessynchro>
+
+This module is always laod and run. It synchronize base given in
+L<latmos-accounts.ini>
+
+=head3 L<LATMOS::Accounts::Task::Dummy>
+
+A fake module for testing
+
+=head3 L<LATMOS::Accounts::Task::Buildlistes>
+
+This use L<la-sync-list.ini> config file to build mailing list file
+typically usuable by C<sync_members> mailman program.
+
+=head3 L<LATMOS::Accounts::Task::Buildnet>
+
+Generate DNS and DHCP files from C<netzone> objects
+
+=head3 L<LATMOS::Accounts::Task::Employment>
+
+This module update user's information according the status matching current
+date.
+
+=head3 L<LATMOS::Accounts::Task::Refreshexpired>
+
+Increasing revision number for user to force update in othere base not supporting
+properly expiration date.
+
+=head3 L<LATMOS::Accounts::Task::Updatedyndata>
+
+Force automatic object (such as group with filter) to be rebuilt in case it depend
+on external base event like date.
+
+=head3 L<LATMOS::Accounts::Task::Delexpiredusers>
+
+Make user's account unexported after expiration delay.
+
+=head3 L<LATMOS::Accounts::Task::Expiredaliases>
+
+Allow to automatically add an alias to user when account get expired.
+The basis idea is to redirect mail to an automatic responder claiming account is
+expired.
+
+=head3 L<LATMOS::Accounts::Task::Stats>
+
+Collect current data according C<Stat> objects definition.
+
+=head3 L<LATMOS::Accounts::Task::Unexportexpired>
+
+For C<Aliases> and <Nethost>, unexport object when expiration date is reached.
+
+=head3 L<LATMOS::Accounts::Task::Unusedhosts>
+
+Unexport C<Nethost> when owner/user is expired or unexported
+
+=head3 L<LATMOS::Accounts::Task::Iprecover>
+
+Delete IP set on nethost unexported and not midified for the specified delay.
+
+=cut
+
+my $needsync = 0;
+my $pidfile = undef;
+$wait ||= 5; # default in minutes
+
+my $syncm = LATMOS::Accounts::SyncManager->new($config) or do {
+    la_log LA_ERR, "Cannot instanciate Sync Manager";
+    exit(1);
+};
+
+la_set_log(
+    syslog => [],
+    console => ($nodaemon ? LA_NOTICE : undef),
+);
+
+# Trap perl message, send it to log
+$SIG{__DIE__} = sub {
+    la_log LA_ERR, "Die: %s", $_[0] unless($_[0] =~ /^Can't locate/);
+    die $_[0];
+};
+$SIG{__WARN__} = sub {
+    la_log LA_WARN, "Warn: %s", $_[0];
+    warn $_[0];
+};
+
+{
+    my $LA = LATMOS::Accounts->new($config, noacl => 1);
+    if ($LA->val('_default_', 'state_dir')) {
+        $pidfile = $LA->val('_default_', 'state_dir') . '/sync-manager.pid';
+    }
+    if (my $mail = $LA->val('_network_', 'maillog')) {
+            la_set_log(mail => $mail);
+    }
+}
+
+# Daemonize
+if (!$nodaemon) {
+    my $pid = fork;
+    if ($pid) {
+        exit 0;
+    }
+}
+
+if ($pidfile) {
+    # maybe we want to lock here !!
+    open(my $fh, '>', $pidfile) or die "Cannot open pidfile $pidfile\n";
+    print $fh "$$\n";
+    close($fh);
+}
+
+$SIG{INT} = sub {
+    unlink($pidfile) if ($pidfile);
+    exit 0;
+};
+
+$SIG{'HUP'} = sub {
+    $needsync = 1;
+    la_log LA_NOTICE, "SigHup received, synchronise now";
+};
+
+sub process {
+    
+    la_log LA_NOTICE, "Start processing modules";
+    my $pid = fork();
+    
+    if ($pid == 0) {
+        $SIG{INT} = 'DEFAULT';
+        my $res = 0;
+        foreach my $module ($syncm->listSortedModules()) {
+            $res = 1 if (!$syncm->process_module($module));
+        }
+        exit($res);
+    }
+
+    my $retpid;
+    while(($retpid = waitpid(-1, 0)) <= 0) {}
+    local $SIG{HUP} = 'IGNORE';
+    if ($retpid) {
+        my $res = $? >> 8;
+        if ($res) {
+            la_log LA_ERR, "Sync process exit with $res";
+            return;
+        }
+    }
+    la_log LA_NOTICE, "End processing modules";
+    return 1;
+}
+
+while (1) {
+    $needsync = 0;
+
+    process(); 
+
+    # waiting, to perform next sync
+    if ($needsync) {
+        la_log(LA_DEBUG, "NeedSync received");
+        next;
+    }
+
+    la_log(LA_DEBUG, "Sleeping $wait minutes");
+    sleep($wait * 60);
+
+}
+
+__END__
+
+=head1 SEE ALSO
+
+L<la-sync-manager.ini>, L<latmos-accounts.ini>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/bin/la-sync-process
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-sync-process	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-sync-process	(revision 2119)
@@ -0,0 +1,29 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Getopt::Long;
+use Pod::Usage;
+use Config::IniFiles;
+use LATMOS::Accounts;
+use LATMOS::Accounts::SyncManager;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Task;
+
+GetOptions(
+    'c|config=s'   => \my $config,
+    'help'         => sub { pod2usage(0) },
+) or pod2usage();
+
+my $needsync = 2;
+
+my $syncm = LATMOS::Accounts::SyncManager->new($config) or do {
+    la_log LA_ERR, "Cannot instanciate Sync Manager";
+    exit(1);
+};
+
+
+foreach my $run (@ARGV) {
+    my $res = $syncm->process_module($run);
+}
+
Index: tags/5.2.23/LATMOS-Accounts/bin/la-test-mail
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-test-mail	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-test-mail	(revision 2119)
@@ -0,0 +1,57 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use LATMOS::Accounts::Mail;
+use Getopt::Long;
+use Pod::Usage;
+use YAML;
+
+=head1 NAME
+
+    la-test-mail - Send mail using LATMOS::Accounts config for testing purpose
+
+=head1 SYNOPSIS
+
+    la-test-mail templatename email@address
+
+=head1 DESCRIPTION
+
+A tools to test mail, especially custom template.
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'help'              => sub { pod2usage(0) },
+    'var=s'             => \my $varsfile,
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item --var yaml-file
+
+An optional path to YAML file containing variable to use to fill template
+placeholders
+
+=back
+
+=cut
+
+my $vars = {};
+if ($varsfile) {
+    $vars = YAML::LoadFile($varsfile) or die "Cannot load $varsfile\n";
+}
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+
+my $lamail = LATMOS::Accounts::Mail->new($LA, $ARGV[0]);
+
+$lamail->process({ to => $ARGV[1] }, $vars);
Index: tags/5.2.23/LATMOS-Accounts/bin/la-warn-expire
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la-warn-expire	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la-warn-expire	(revision 2119)
@@ -0,0 +1,117 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Maintenance;
+use LATMOS::Accounts::Log;
+use Getopt::Long;
+use Pod::Usage;
+
+=head1 NAME
+
+    la-warn-expire - query expiration of user and send mail
+
+=head1 SYNOPSIS
+
+    la-warn-expire [options]
+
+=cut
+
+GetOptions(
+    'c|config=s' => \my $config,
+#    'b|base=s'   => \my $base,
+    'm|mail'     => \my $mail,
+    'help'       => sub { pod2usage(0) },
+    'users|u=s'  => \my @users,
+    'to=s'       => \my $to,
+    'at=s'       => \my $delay,
+    'test'       => \my $test,
+) or pod2usage();
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c|--config configdir
+
+Use this configuration directory instead of the default one.
+
+=item -m|mail
+
+Send a mail to user(s) concerned by the future expiration of his/her/their account(s).
+
+=item -u|user USER
+
+Send email only to this user if his/her account will expire. Usefull for
+testing and not sending hundred of mails.
+
+Multiple users can be specified by using -u several times
+
+=item --to MAIL
+
+Instead sending email to user(s) and eventually his/her/their manager(s), send the mail to
+only this email address.
+
+=item --at DELAY
+
+Search account expiring in DELAY. Delay must must a number follow by a time
+unit. Example:
+
+    1 month
+    2 days
+
+=item --test
+
+With -m, do not send mail but tell to who the mail would have been sent
+
+=back
+
+=head1 CONFIGURATION
+
+Some variables are taken from configuration directory:
+
+=over 4
+
+=item mailFrom
+
+Specify the From address to use in mail
+
+=item smtp
+
+The smtp server to use to send mail
+
+=item alwayscc
+
+In normal mode (without --to), the mail is also sent this email address.
+
+=back
+
+=head1 USERS EMAIL ADDRESSES
+
+The mail use to send warning to users and manager is taken from 'mail'
+attributes. If the attributes is unset, the user will not receive the mail.
+
+=cut
+
+if (!$test) {
+    la_set_log(
+        syslog => [],
+        console => undef,
+    );
+}
+
+my $LA = LATMOS::Accounts::Maintenance->new($config);
+
+if ($mail) {
+    $LA->warn_next_expire_users(
+        to => $to,
+        users => (@users ? \@users : undef),
+        delay => $delay,
+        test => $test,
+    );
+} else {
+    foreach ($LA->find_next_expire_users($delay)) {
+        printf("%4s jours: %s (%s)\n", $_->{days}, $_->{obj}->get_c_field('displayName'),
+            $_->{obj}->id);
+    }
+}
Index: tags/5.2.23/LATMOS-Accounts/bin/la2xls
===================================================================
--- tags/5.2.23/LATMOS-Accounts/bin/la2xls	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/bin/la2xls	(revision 2119)
@@ -0,0 +1,133 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use Getopt::Long;
+use Pod::Usage;
+use Spreadsheet::WriteExcel;
+
+=head1 NAME
+
+    la2xls - Output data in XLS format
+
+=head1 SYNOPSIS
+
+    la-search [options] filter [filter2 [...]] [attributes [...]]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --config configdir
+
+Use this configuration directory instead default
+
+=item --base basename
+
+Perform search in this specific base instead default
+
+=item --with-unexp
+
+Take into account all objects (even non propagated ones, with attribute
+'exported'=0) (default)
+
+=item --wo-unexp
+
+Take into account only propagated objects (attribute 'exported'=1)
+
+=item -f FILE
+
+The output filename. The default is la2xls.xls.
+
+=item FILTER
+
+Filter are in form "[ATTRIBUTE][OPERATOR][VALUE]"
+
+where attribute is a supported attribute.
+
+OPERATOR can be:
+
+=over 4
+
+=item C<=>
+
+Search object having attribute strictely equal to VALUE. If VALUE is C<*> match
+any non empty value.
+
+=item C<~>
+
+Search object having attribute containing VALUE.
+
+=back
+
+=back
+
+=cut
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'o|object=s'        => \my $otype,
+    'no-unexp|wo-unexp' => \my $nounexp,
+    'with-unexp'        => \my $unexp,
+    'f|file=s'          => \my $file,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+if (!$ARGV[0]) {warn "You must specify 'filter', aborting\n"; pod2usage(); }
+
+$otype ||= 'user';
+$file  ||= 'la2xls.xls';
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $LA->base($base);
+$labase && $labase->load or die "Cannot load base";
+
+$labase->unexported($nounexp ? 0 : 1);
+
+my (@filters, @attr);
+
+foreach (@ARGV) {
+    if (/(~|=)/) {
+        push(@filters, $_);
+    } else {
+        push(@attr, $_);
+    }
+}
+
+my @result = $labase->search_objects($otype, @filters);
+
+my @objs = map { $labase->get_object($otype, $_) } @result;
+
+# Create a new Excel workbook
+my $workbook = Spreadsheet::WriteExcel->new($file);
+
+# Add a worksheet
+my $worksheet = $workbook->add_worksheet();
+
+#  Add and define a format
+my $format = $workbook->add_format(); # Add a format
+$format->set_bold();
+$format->set_align('center');
+
+$worksheet->write(0, 0, 'name', $format);
+my $row = 0;
+foreach my $obj (@objs) {
+    $row++;
+    $worksheet->write($row, 0, $obj->id);
+}
+
+
+
+foreach my $col (0 .. $#attr) {
+    $worksheet->write(0, $col+1, $attr[$col], $format);
+
+    my $row = 0;
+    foreach my $obj (@objs) {
+        $row++;
+        $worksheet->write($row, $col+1, $obj->get_attributes($attr[$col]));
+    }
+}
+
+$workbook->close();
Index: tags/5.2.23/LATMOS-Accounts/etc/cron.d/latmos-accounts
===================================================================
--- tags/5.2.23/LATMOS-Accounts/etc/cron.d/latmos-accounts	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/etc/cron.d/latmos-accounts	(revision 2119)
@@ -0,0 +1,2 @@
+# $Id$
+0 8 * * 1 latmos la-warn-expire -m
Index: tags/5.2.23/LATMOS-Accounts/etc/init.d/la-sync-manager
===================================================================
--- tags/5.2.23/LATMOS-Accounts/etc/init.d/la-sync-manager	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/etc/init.d/la-sync-manager	(revision 2119)
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# la-sync-manager startup script
+#
+# chkconfig: 345 97 3
+# description: Manage synchronisation between latmos-account bases
+
+. /etc/rc.d/init.d/functions
+[ -r /etc/sysconfig/i18n ] && . /etc/sysconfig/i18n
+export LC_ALL
+export LANG
+
+USER=nobody
+[ -f /etc/sysconfig/latmos-accounts ] && \
+    . /etc/sysconfig/latmos-accounts
+
+RETVAL=0
+prog=la-sync-manager
+
+function start () {
+    gprintf "Starting %s: " "$prog"
+    daemon --user $USER /usr/bin/la-sync-manager
+    [ $? -eq 0 ] && success || failure
+    touch /var/lock/subsys/$prog
+    echo
+}
+
+function stop () {
+    gprintf "Stopping %s: " "$prog"
+    killproc la-sync-manager
+    rm -f /var/lock/subsys/$prog
+    echo
+}
+
+case "$1" in
+    start)
+	start
+	;;
+    stop)
+	stop
+	;;
+    status)
+	status la-sync-manager
+	;;
+    restart)
+	stop
+	start
+	;;
+    reload)
+        la-sync -b
+	;;
+    *)
+	gprintf "Usage: $0 {start|stop|status|restart|reload}\n"
+	exit 1
+esac
+
+exit $RETVAL
Index: tags/5.2.23/LATMOS-Accounts/etc/sysconfig/latmos-accounts
===================================================================
--- tags/5.2.23/LATMOS-Accounts/etc/sysconfig/latmos-accounts	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/etc/sysconfig/latmos-accounts	(revision 2119)
@@ -0,0 +1,7 @@
+# The user server must run as
+USER=nobody
+
+# The listener port
+PORT=4000
+
+
Index: tags/5.2.23/LATMOS-Accounts/latmos-accounts.spec.in
===================================================================
--- tags/5.2.23/LATMOS-Accounts/latmos-accounts.spec.in	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/latmos-accounts.spec.in	(revision 2119)
@@ -0,0 +1,106 @@
+%define realname   LATMOS-Accounts
+%define version    @VERSION@
+%define release    %mkrel %{?svnrelease:0.%{svnrelease}}%{?!svnrelease:1}
+
+Name:       latmos-accounts
+Version:    %{version}
+Release:    %{release}
+License:    GPL or Artistic
+Group:      Development/Perl
+Summary:    Latmos Account system
+Source:     %{realname}-%{version}.tar.gz
+Url:        http://search.cpan.org/dist/%{realname}
+BuildRoot:  %{_tmppath}/%{name}-%{version}-%{release}-buildroot
+BuildArch:  noarch
+
+BuildRequires: perl(Net::LDAP)
+BuildRequires: perl(DBD::Pg)
+BuildRequires: perl(Unicode::Map8)
+BuildRequires: perl(Crypt::RSA)
+BuildRequires: perl(MIME::Base64)
+BuildRequires: perl(Term::ReadKey)
+BuildRequires: perl(Config::IniFiles)
+BuildRequires: perl(Mail::Sendmail)
+BuildRequires: perl(Net::DNS)
+BuildRequires: perl(Template)
+BuildRequires: perl(Net::IP)
+BuildRequires: perl(Text::CSV_XS)
+BuildRequires: perl(Heimdal::Kadm5)
+BuildRequires: perl(Crypt::Cracklib)
+BuildRequires: perl(Net::IPv4Addr)
+BuildRequires: perl(Net::IPv6Addr)
+BuildRequires: perl(Math::Base85)
+BuildRequires: perl(DateTime)
+BuildRequires: perl(SOAP::Lite)
+BuildRequires: perl(HTTP::Cookies)
+BuildRequires: perl(XML::XPath)
+BuildRequires: perl(Locale::gettext)
+BuildRequires: perl-Locale-Maketext-Lexicon 
+BuildRequires: perl(Crypt::SmbHash)
+BuildRequires: perl(Date::Calc)
+BuildRequires: perl(Spreadsheet::WriteExcel)
+BuildRequires: perl(Text::CSV)
+
+Requires: perl(Mail::Sendmail)
+Requires: perl(Net::DNS)
+Requires: perl(Template)
+Requires: perl(DBD::Pg)
+Requires: perl(IO::Socket::SSL)
+Requires: perl-Term-ReadLine-Gnu
+Requires: perl(Math::Base85)
+
+%description
+The core applications tools of the LATMOS-Accounts system. A batch of
+applucations to create user accounts and exchange information through several
+others accounts management system such ldap, nis, etc...
+
+%prep
+%setup -q -n %{realname}-%{version} 
+
+%build
+%{__perl} Makefile.PL INSTALLDIRS=vendor
+%make
+
+%check
+make test
+
+%install
+rm -rf %buildroot
+%makeinstall_std
+
+mkdir %buildroot%_sysconfdir/init.d
+install -m 755 etc/init.d/la-sync-manager %buildroot%_sysconfdir/init.d/la-sync-manager
+
+mkdir -p %buildroot/%_sysconfdir/sysconfig 
+install -m 600 etc/sysconfig/latmos-accounts %buildroot/%_sysconfdir/sysconfig/latmos-accounts
+
+mkdir -p %buildroot/%_sysconfdir/cron.d
+install -m 644 etc/cron.d/latmos-accounts %buildroot/%_sysconfdir/cron.d/latmos-accounts
+
+%clean
+rm -rf %buildroot
+
+%preun
+%_preun_service la-sync-manager
+
+%post
+%_post_service la-sync-manager
+if [ "$1" -ge "1" ] ; then
+    /sbin/service la-sync-manager restart
+fi
+
+%files
+%defattr(-,root,root)
+%doc README
+%_sysconfdir/init.d/la-sync-manager
+%config(noreplace) %_sysconfdir/sysconfig/latmos-accounts
+%config(noreplace) %_sysconfdir/cron.d/latmos-accounts
+%attr(0600,laccounts,laccounts) %config(noreplace) %_sysconfdir/latmos-accounts/*
+%_bindir/*
+%{_mandir}/man1/*
+%{_mandir}/man3/*
+%{_mandir}/man5/*
+%{_mandir}/man8/*
+%perl_vendorlib/*
+%_datadir/latmos-accounts
+%_datadir/locale/*
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts.pm	(revision 2119)
@@ -0,0 +1,332 @@
+package LATMOS::Accounts;
+
+use 5.010000;
+use strict;
+use warnings;
+use base qw(Config::IniFiles);
+use LATMOS::Accounts::Bases;
+use LATMOS::Accounts::Synchro;
+use LATMOS::Accounts::SynchAccess;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Acls;
+use LATMOS::Accounts::I18N;
+
+our $VERSION = '5.2.23';
+
+=head1 NAME
+
+LATMOS::Accounts - Core module for LATMOS account management tools
+
+=head1 DESCRITPTION
+
+=cut
+
+# Return the configuration directory according settings:
+#
+sub _configdir {
+   my ($self) = @_;
+   $ENV{LA_CONFIG} ||
+   ($self || {})->{_configdir} ||
+   '/etc/latmos-accounts'
+}
+
+=head1 FUNCTION
+
+=head2 new($configdir, %options)
+
+Instanciate a new LATMOS::Accounts object.
+
+C<$configdir> if defined is the directory containing files to use,
+default to F</etc/latmos-accounts/>.
+
+C<%options> can contains:
+
+=over 4
+
+=item noacl
+
+If true, acls configuration are not load and code act like everything is
+allowed.
+
+This flag is usefull for administrative tools, for which no acl must apply.
+
+=back
+
+=cut
+
+sub new {
+    my ($class, $config, %options) = @_;
+
+    $config ||= _configdir();
+
+    my $configfile = join('/', $config, 'latmos-accounts.ini');
+
+    my $self = Config::IniFiles->new(
+        -file => $configfile,
+        '-default' => '_default_',
+    ) or do {
+        la_log(LA_ERR, 'Can\'t open main config file %s', $configfile);
+        return;
+    };
+
+    $self->{_configdir} = $config;
+    bless($self, $class);
+
+    unless ($options{noacl}) {
+        if (-f (my $aclf = join('/', $self->_configdir, 'la-acls.ini'))) {
+            $self->{_acls} = LATMOS::Accounts::Acls->new($aclf) or do {
+                la_log(LA_ERR, 'Cannot load ACL file %s', $aclf);
+                return;
+            };
+        }
+    }
+
+    if (-f (my $allowf = join('/', $self->_configdir, 'la-allowed-values.ini'))) {
+        $self->{_allowed_values} = Config::IniFiles->new(
+            -file => $allowf,
+        ) or do {
+            la_log(LA_ERR, 'Cannot load ALLOWED VALUES %s', $allowf);
+            return;
+        };
+    }
+
+    $self
+}
+
+=head2 list_bases
+
+Return the base list found in config file
+
+=cut
+
+sub list_bases {
+    my ($self) = @_;
+    grep {
+        !m/^_.*_$/ &&
+        !m/^sync:/
+    } $self->Sections
+}
+
+=head2 default_base_name
+
+Return the default base name according config file
+
+=cut
+
+sub default_base_name {
+    my ($self) = @_;
+    $self->val('_default_', 'base', ($self->list_bases)[0]);
+}
+
+=head2 base($basename)
+
+Return a L<LATMOS::Accounts::Base> object over base named $basename
+defined in the config file.
+
+The base is loaded by this function.
+
+=cut
+
+sub base {
+    my ($self, $section) = @_;
+    # this method perform a cache
+    $self->_load_base($section || $self->default_base_name);
+}
+
+# do the bad work
+sub _load_base {
+    my ($self, $section) = @_;
+    my $type = $self->val($section, 'type') or return;
+    la_log(LA_DEBUG, "Trying to load base %s", $section);
+
+    my %params =
+        map { $_ => ($self->val($section, $_)) }
+        ($self->Parameters($section), $self->Parameters('_default_'));
+ 
+    my %defattr =
+        map { $_ => ($self->val('_defattr_', $_)) }
+        $self->Parameters('_defattr_');
+
+    my $base = LATMOS::Accounts::Bases->new(
+        $type,
+        {
+            params => \%params,
+            label => $section,
+            acls => $self->{_acls},
+            allowed_values => $self->{_allowed_values},
+            configdir => $self->_configdir,
+            la => $self,
+            defattr => { %defattr },
+        },
+    ) or do {
+        la_log(LA_WARN, "Cannot instanciate base $section ($type)");
+        return;
+    };
+    $base->load or do {
+        la_log(LA_ERR, "%s didn't load", $base->label);
+        return;
+    };
+    $base;
+}
+
+=head2 list_synchro
+
+List synchronisation setup in L<latmos-accounts.ini>
+
+=cut
+
+sub list_synchro {
+    my ($self) = @_;
+    grep { $_ } map { /^sync:(.*)$/; $1 } $self->Sections
+}
+
+=head2 default_synchro_name
+
+Return de default synchronisation name
+
+=cut
+
+sub default_synchro_name {
+    my ($self) = @_;
+    $self->val('_default_', 'sync');
+}
+
+=head2 default_synchro
+
+Return a reference to default synchronisation object
+
+=cut
+
+sub default_synchro {
+    my ($self, %options) = @_;
+    my $syncname = $self->default_synchro_name or do {
+        la_log(LA_ERR, 'Cannot find default synchro in config');
+        return;
+    };
+    $self->create_synchro($syncname, %options);
+}
+
+=head2 create_synchro($name, %options)
+
+Return a reference to synchronisation object for C<$name> synchronisation.
+
+=cut
+
+sub create_synchro {
+    my ($self, $name, %options) = @_;
+
+    # taking options from config
+    if ($name) {
+        foreach my $param ($self->Parameters("sync:$name")) {
+            if (!defined($options{$param})) {
+                my @args = $self->val("sync:$name", $param);
+                $options{$param} = ($args[1] || $param eq 'to')
+                    ? [ @args ]
+                    : $args[0];
+            }
+        }
+    }
+
+    my $labfrom = $options{from} ? $self->base($options{from}) : $self->base;
+
+    my @labto =
+        grep { $_ }
+        map { $self->base($_) }
+        @{ $options{to} || []}
+        or do {
+        la_log(LA_ERR, "No destination base load in this synchro");
+        return;
+    };
+
+    my $sync = LATMOS::Accounts::Synchro->new(
+        $labfrom, [ @labto ],
+        state_dir => ($self->val('_default_', 'state_dir') || undef),
+        %options,
+        name => $name,
+    );
+}
+
+sub _sync_from_name {
+    my ($self, $syncname) = @_;
+    return if (!$syncname);
+    $self->val("sync:$syncname", 'from', $self->default_base_name);
+}
+
+=head2 sync_access($name, %options)
+
+Return a L<LATMOS::Accounts::SynchAccess> object over C<$name> synchronisation.
+
+=cut
+
+sub sync_access {
+    my ($self, $name, %options) = @_;
+
+    my @obases;
+    if ($name) {
+        @obases =
+        (map { $self->base($_) } ($self->_sync_from_name($name), $self->val("sync:$name", 'to')));
+    } elsif(@{ $options{bases} || []}) {
+        @obases = map { $self->base($_) } @{ $options{bases} || []};
+    } elsif (my $sname = $self->default_synchro_name) {
+        @obases = (map { $self->base($_) }
+            ($self->_sync_from_name($sname), $self->val("sync:$sname", 'to'))
+        );
+    }
+
+    if (!@obases) {
+        la_log(LA_ERR, "Cannot load any bases for syncronised action");
+        return;
+    }
+
+    la_log(LA_DEBUG, "Load databases: %s", join(', ', map { $_->label } @obases));
+
+    LATMOS::Accounts::SynchAccess->new([ @obases ]);
+}
+
+=head2 call_batch_sync
+
+Send signal to L<la-sync-manager> daemon to synchronize bases.
+
+=cut
+
+sub call_batch_sync {
+    my ($self) = @_;
+    if (my $sd = $self->val('_default_', 'state_dir')) {
+        if (open(my $fh, '<', $sd . '/sync-manager.pid')) {
+            my $pid = <$fh> || '';
+            chomp($pid);
+            close($fh);
+            if ($pid && kill 1, $pid) {
+                return 1; # \o/ we succeed
+            } else {
+                la_log(LA_ERR, "Can send signal -1 to la-sync-manager (pid: %s, %s)",
+                    $pid || 'none', $!);
+                return;
+            }
+        } else {
+            la_log(LA_ERR, 'Cannot open la-sync-manager pid file');
+            return;
+        }
+    } else {
+        la_log(LA_WARN, "No statedir setup, cannot find la-sync-manager pid file");
+        return;
+    }
+}
+
+1;
+
+__END__
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009, 2010, 2011, 2012 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Acls.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Acls.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Acls.pm	(revision 2119)
@@ -0,0 +1,208 @@
+package LATMOS::Accounts::Acls;
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Acls::Acl;
+
+our $VERSION = (q$Rev: 330 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Acls - Acl support in L<LATMOS::Accounts>
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 new ($file)
+
+Instanciate Acls from C<$file>
+
+=cut
+
+sub new {
+    my ($class, $file) = @_;
+    my $acls = bless(
+        {
+            _acls => []
+        }, $class
+    );
+    if ($file) {
+        $acls->read_acl_file($file) or return;
+    }
+    $acls
+}
+
+=head1 ACL FILE FORMAT
+
+    OBJ_TYPE.ATTRIBUTE
+        USER:  read,write
+        USER2: read
+
+=over 4
+
+=item OBJ_TYPE is the type the object, '*' match all
+
+=item ATTRIBUTE is either an attribute, either an comma separate attribute
+enclose into bracket, either a '*' to match any attribute.
+
+Special keyword C<@CREATE> and C<@DELETE> can be used to allow or deny object
+creation and deletion. In this case read permission and USER in form C<$...> for
+C<@CREATE> have no effect (see below). C<*> do not include C<@CREATE> and
+C<@DELETE> action.
+
+=item USER can be
+
+=over 4
+
+=item a username
+
+=item group, prefixed by a '%'
+
+=item an attribute of the accessed object which should contains the username,
+prefixed by '$'
+
+=item C<*> to math any user
+
+=item @authenticated or @anonymous for any authenticated user and non
+authenticated user ('*' include both)
+
+=back
+
+=item Permission are read and or write, or deny.
+
+=back
+
+ACL are applied in the order they appear in the file.
+
+=cut
+
+=head2 read_acl_file ($file)
+
+Load acls from file C<$file>.
+
+=cut
+
+sub read_acl_file {
+    my ($self, $file) = @_;
+
+    my $acl;
+    my $line_nb = 0;
+    my $prevline = "";
+    if (open(my $handle, '<', $file)) {
+        while(my $realline = <$handle>) {
+            $line_nb++;
+            chomp($realline);
+            if ($realline =~ /\\$/) {
+                # continuation line
+                $prevline .= $realline;
+                $prevline =~ s/\s*\\$//;
+                next;
+            }
+            my $line = $prevline . $realline; # keep track to report malformed file
+            $prevline = "";
+            $line =~ s/\s*#.*//;
+            if ($line =~ /^(\S.*)/) {
+                if ($acl) {
+                    push(@{$self->{_acls}}, $acl);
+                }
+                $acl = LATMOS::Accounts::Acls::Acl->new($1) or do {
+                    la_log(LA_ERR, "Error in %s at line %d", $file,
+                        $line_nb);
+                    return;
+                };
+            } elsif ($line =~ /^\s+(\S.*)/) {
+                if ($acl) {
+                    $acl->add_perm($line) or do {
+                        la_log(LA_ERR, "Error in %s at line %d", $file,
+                            $line_nb);
+                        return;
+                    };
+                } else {
+                    # err no acl
+                    return;
+                }
+            } elsif ($line =~ /^\s*$/) {
+                # just empty line
+            } else {
+            }
+        }
+        if ($acl) {
+            push(@{$self->{_acls}}, $acl);
+        }
+    } else {
+        la_log(LA_ERR, "Cannot open acl file %s", $file);
+        return;
+    }
+    1;
+}
+
+=head2 add ($obj_dot_attr, $list)
+
+A new acl for C<$obj_dot_attr> (in form C<object.attribute>) with C<$list>
+permissions.
+
+=cut
+
+sub add {
+    my ($self, $obj, $list) = @_;
+    my $acl = LATMOS::Accounts::Acls::Acl->new(
+        $obj, $list) or return;
+    push(@{$self->{_acls}}, $acl);
+    return 1;
+}
+
+=head2 check ($obj, $attr, $perm, $who, $groups)
+
+Return true is this acl apply to C<$obj>/C<$attr> for C<$perm> by user
+C<$who> in groups C<$groups>.
+
+=cut
+
+sub check {
+    my ($self, $obj, $attr, $perm, $who, $groups) = @_;
+    # Asking 'r' perm over create or delete has no sense:
+    $attr =~ /^@(CREATE|DELETE)$/ && $perm eq 'r' and return;
+
+    foreach my $acl (@{$self->{_acls}}) {
+        my $res = $acl->match($obj, $attr, $perm, $who, $groups);
+        defined($res) and return $res;
+    }
+    return 0;
+}
+
+=head2 dump
+
+Return currently load acl as text
+
+=cut
+
+sub dump {
+    my ($self) = @_;
+    foreach my $acl (@{$self->{_acls}}) {
+        print $acl->dump, "\n";
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Acls::Acl>
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009, 2010, 2011, 2012 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Acls/Acl.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Acls/Acl.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Acls/Acl.pm	(revision 2119)
@@ -0,0 +1,154 @@
+package LATMOS::Accounts::Acls::Acl;
+use strict;
+use warnings;
+
+=head1 NAME
+
+LATMOS::Accounts::Acls::Acl - Single acl entry
+
+=head1 DESCRIPTION
+
+Manage a single acl entry
+
+=head1 FUNCTIONS
+
+=head2 new ($objdotatt, $list)
+
+Create new sub acl where C<$objdotatt> is object in form C<Object.Attribute> and
+C<$list> the list of permission.
+
+=cut
+
+sub new {
+    my ($class, $objdotatt, $list) = @_;
+    my ($obj, $attr) = $objdotatt =~ /^([^.]+)\.(.*)/ or return;
+    my @attrs = ($attr =~ /^\{(.*)\}$/)
+        ? (split(/\s*,\s*/, $1))
+        : ($attr);
+    @attrs or return;
+    my $acl = {
+        obj => lc($obj),
+        attr => [ map { lc } @attrs ],
+        users => [], # user->{r} = 1
+    };
+    bless($acl, $class);
+    foreach my $k (@{ $list || [] }) {
+        $acl->add_perm($k) or return;
+    }
+    $acl
+}
+
+=head2 add_perm($perm)
+
+Add a permission to this sub acl
+
+=cut
+
+sub add_perm {
+    my ($self, $perm) = @_;
+    my ($username, $perms) = $perm =~ /^\s*(\S*):\s*(.*)$/;
+    $username && $perms or return;
+    my $user = { user => $username };
+    foreach (split(/\s*,\s*/, $perms)) {
+        /^read$/i  and do { $user->{r} = 1; next; };
+        /^write$/i and do { $user->{w} = 1; next; };
+        /^deny$/i and do {
+            # reseting...
+            $user->{r} = 0;
+            $user->{w} = 0;
+            last; # we end here, life is hard
+        };
+        return;
+    }
+    push(@{$self->{users}}, $user);
+    1
+}
+
+=head2 match($obj, $attr, $perm, $who, $groups)
+
+Return true is this sub acl apply to C<$obj>/C<$attr> for C<$perm> by user
+C<$who> in groups C<$groups>.
+
+=cut
+
+sub match {
+    my ($self, $obj, $attr, $perm, $who, $groups) = @_;
+    my $objtype = ref $obj ? lc($obj->type) : $obj;
+    $attr = lc($attr);
+
+    # Does this ACL series concern this object:
+    if (!($self->{obj} eq '*' || $self->{obj} eq $objtype)) {
+        return
+    }
+    # Does this ACL series concern this attribute:
+    grep { $_ eq '*' || $_ eq $attr } @{$self->{attr}} or return;
+
+    # Foreach user, testing if this permission match:
+    foreach my $u (@{ $self->{users} }) {
+        # Obj have attr eq login user
+        if (substr($u->{user}, 0, 1) eq '$') { # check attr content
+            if (ref $obj) {
+                my $attribute = substr($u->{user}, 1);
+                my $val = $obj->_get_c_field($attribute) or return;
+                my @vals = ref $val ? (@{ $val }) : ($val);
+                return $u->{$perm} if (defined($u->{$perm}) && grep { $_ eq $who } @vals);
+            }
+        # user is in group
+        } elsif (substr($u->{user}, 0, 1) eq '%') { # group
+            my $group = substr($u->{user}, 1);
+            return $u->{$perm} if (defined($u->{$perm}) && grep { $group eq $_ } grep { $_ } @{$groups ||[]});
+        # any user
+        } elsif ($u->{user} eq '*' || $u->{user} eq $who) {
+            # TODO deference alias for SQL base
+            # $obj->base->get_objects(...) ?
+            return $u->{$perm} if (defined($u->{$perm}));
+        # any authenticated user
+        } elsif (lc($u->{user}) eq '@authenticated' && $who) {
+            return $u->{$perm} if (defined($u->{$perm}));
+        # not login
+        } elsif (lc($u->{user}) eq '@anonymous' && $who eq "") {
+            return $u->{$perm} if (defined($u->{$perm}));
+        }
+    }
+    return;
+}
+
+=head2 dump
+
+Return a textual dump of the sub acl
+
+=cut
+
+sub dump {
+    my ($self) = @_;
+    my $dump = sprintf("%s.{%s}\n", $self->{obj}, join(', ', @{$self->{attr}}));
+    foreach my $u (@{ $self->{users} }) {
+        $dump .= sprintf("\t%s: %s\n",
+            $u->{user},
+            join(', ', ($u->{r} ? 'read' : ()), ($u->{w} ? 'write' : ())) ||
+            'deny');
+    }
+    $dump
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Acls>
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009, 2010, 2011, 2012 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases.pm	(revision 2119)
@@ -0,0 +1,1263 @@
+package LATMOS::Accounts::Bases;
+
+use 5.010000;
+use strict;
+use warnings;
+use LATMOS::Accounts::Bases::Objects;
+use LATMOS::Accounts::Bases::Attributes;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils qw( exec_command to_ascii );
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases - Base class for account data bases
+
+=head1 SYNOPSIS
+
+  use LATMOS::Accounts::Bases;
+  my $base = LATMOS::Accounts::Bases->new('type', %options);
+  ...
+
+=head1 DESCRIPTION
+
+This module provide basic functions for various account base
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 new($type, %options)
+
+Return, if success, a new data base account object, $type is
+account base type, %options to setup the base.
+
+=cut
+
+sub new {
+    my ($class, $type, $options) = @_;
+
+    my $pclass = ucfirst(lc($type));
+    eval "require LATMOS::Accounts::Bases::$pclass;";
+    if ($@) {
+        la_log(LA_DEBUG, "Failed to load base type `%s': %s", $type, $@);
+        return
+    }
+    my $base = "LATMOS::Accounts::Bases::$pclass"->new(%{$options->{params}})
+        or return;
+    $base->{_type} = lc($pclass);
+    $base->{_label} = $options->{label};
+    $base->{_options} = $options->{params};
+    $base->{wexported} = 0;
+    $base->{defattr} = $options->{defattr};
+    $base->{_acls} = $options->{acls};
+    $base->{_allowed_values} = $options->{allowed_values};
+    $base->{_la} = $options->{la};
+    la_log(LA_DEBUG, 'Instanciate base %s (%s)', ($base->label || 'N/A'), $pclass);
+    $base
+}
+
+=head2 load
+
+Make account base loading data into memory if need.
+Should always be called, if database fetch data on the fly
+(SQL, LDAP), the function just return True.
+
+=cut
+
+# override by the database driver if need
+sub load { 1 }
+
+=head2 label
+
+Return the database label
+
+=cut
+
+sub label {
+    $_[0]->{_label} || 'NoLabel';
+}
+
+=head2 type
+
+Return the type of the base
+
+=cut
+
+sub type {
+    $_[0]->{_type};
+}
+
+=head2 la
+
+return LATMOS::Accounts object parent to the base
+
+=cut
+
+sub la { $_[0]->{_la} };
+
+=head2 config ($opt)
+
+Return options from config
+
+=cut
+
+sub config {
+    my ($self, $opt) = @_;
+    return $self->{_options}{$opt};
+}
+
+=head2 wexported
+
+See L</unexported>
+
+=cut
+
+sub wexported { unexported(@_) }
+
+=head2 unexported ($wexported)
+
+Set base to report unexported object or not
+
+=cut
+
+sub unexported {
+    my ($self, $wexported) = @_;
+    my $old = $self->{wexported};
+    if (defined($wexported)) {
+        $self->{wexported} = $wexported;
+        $self->log(LA_DEBUG, "Switching exported mode: %s => %s", $old,
+            $wexported);
+    }
+    return($old || 0);
+}
+
+=head2 temp_switch_unexported($CODE, $value)
+
+Switch the base to unexported mode given by C<$value>, run C<$CODE>, restore
+back the previous state and return the result of code ref.
+
+=cut
+
+sub temp_switch_unexported (&;$) {
+    my ($self, $sub, $value) = @_;
+
+    my $old = $self->unexported($value || 0);
+    my $res = $sub->();
+    $self->unexported($old);
+    return $res;
+}
+
+=head2 log($level, $msg, $arg)
+
+Log a message prefixed by database information
+
+=cut
+
+sub log {
+    my ($self, $level, $msg, @args) = @_;
+    my $prefix = 'Base(' . $self->type . '/' . $self->label . ')';
+    LATMOS::Accounts::Log::la_log($level, "$prefix $msg", @args);
+}
+
+=head2 ReportChange($otype, $name, $ref, $changetype, $message, @args)
+
+Functions to report back
+
+=cut 
+
+sub ReportChange {}
+
+=head2 list_supported_objects(@otype)
+
+Return a list of supported object
+
+@type is an additionnal list of objects to check
+
+=cut
+
+sub list_supported_objects {
+    my ($self, @otype) = @_;
+    my %res;
+    foreach my $inc (@INC) {
+        my $sub = 'LATMOS::Accounts::Bases::' . ucfirst($self->type);
+        $sub =~ s/::/\//g;
+        foreach (glob("$inc/$sub/[A-Z]*.pm")) {
+            s/.*\///;
+            s/\.pm$//;
+            $res{lc($_)} = 1;
+        }
+    }
+    $res{$_} = 1 foreach(@otype);
+    my @sobj = grep { $self->is_supported_object($_) } keys %res;
+    la_log(LA_DEBUG, "Base %s supported objects: %s", $self->type, join(', ', @sobj));
+    return @sobj;
+}
+
+=head2 ListSubObjectOType($otype)
+
+Return a list of sub object type supported by C<otype> objects.
+
+=cut
+
+sub ListSubObjectOType {
+    my ($self, $otype) = @_;
+
+    # finding perl class:
+    my $pclass = $self->_load_obj_class($otype) or return;
+    my $definition = "$pclass"->GetOtypeDef($self) or return;
+
+    return keys %{ $definition || {} }
+}
+
+=head2 GetSubObjectKey($otype, $sotype)
+
+Return the key linking sub object C<$sotype> to object type C<$otype>
+
+=cut
+
+sub GetSubObjectKey {
+    my ($self, $otype, $sotype) = @_;
+
+    # finding perl class:
+    my $pclass = $self->_load_obj_class($otype) or return;
+    my $definition = "$pclass"->GetOtypeDef($self, $sotype) or return;
+
+    return $definition->{$sotype};
+}
+
+
+=head2 ordered_objects
+
+Return supported object type ordered in best order for synchronisation
+
+=cut
+
+sub ordered_objects {
+    my ($self) = @_;
+
+    my %deps;
+    my %maxdeps;
+    my @objs = sort { $b cmp $a } $self->list_supported_objects;
+    foreach my $obj (@objs) {
+        foreach my $at ($self->list_canonical_fields($obj)) {
+            my $attr = $self->attribute($obj, $at);
+            $attr->ro and next;
+            $attr->{delayed} and next;
+            if (my $res = $attr->reference) {
+                $deps{$obj}{$res} ||= 1;
+                if ($attr->mandatory) {
+                    $deps{$obj}{$res} = 2;
+                    $maxdeps{$res} = 1;
+                }
+            }
+        }
+    }
+
+    sort {
+        if (keys %{$deps{$a} || {}}) {
+            if (keys %{$deps{$b} || {}}) {
+                return (
+                    ($deps{$a}{$b} || 0) > ($deps{$b}{$a} || 0) ?  1 :
+                    ($deps{$b}{$a} || 0) > ($deps{$a}{$b} || 0) ? -1 :
+                    ($maxdeps{$b} || 0) - ($maxdeps{$a} || 0)
+                );
+            } else {
+                return 1;
+            }
+        } elsif (keys %{$deps{$b} || {}}) {
+            return -1;
+        } else {
+            return  ($maxdeps{$b} || 0) - ($maxdeps{$a} || 0)
+        }
+    } @objs;
+}
+
+sub _load_obj_class {
+    my ($self, $otype) = @_;
+
+    $otype or die "No type given: " . join(', ', caller) . "\n";
+    # finding perl class:
+    my $pclass = ref $self;
+    $pclass .= '::' . ucfirst(lc($otype));
+    eval "require $pclass;";
+    if ($@) {
+        $self->log(LA_DEBUG, 'Cannot load perl class %s', $pclass);
+        return
+    } # error message ?
+    return $pclass;
+}
+
+
+=head2 is_supported_object($otype)
+
+Return true is object type $otype is supported
+
+=cut
+
+sub is_supported_object {
+    my ($self, $otype) = @_;
+    
+    if (my $pclass = $self->_load_obj_class($otype)) {
+        if ($pclass->can('is_supported')) {
+            return $pclass->is_supported($self);
+        } else {
+            return 1;
+        }
+    } else {
+        return 0;
+    }
+}
+
+=head2 list_objects($otype, %options)
+
+Return the list of UID for object of $otype.
+
+=cut
+
+sub list_objects {
+    my ($self, $otype) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    $pclass->list($self);
+}
+
+=head2 listRealObjects
+
+Return the list of UID for object of $otype, alias objects are not return
+
+Options depend of database support
+
+=cut
+
+sub listRealObjects {
+    my ($self, $otype) = @_;
+    $self->list_objects($otype);
+}
+
+=head2 get_object($type, $id)
+
+Return an object of $type (typically user or group) having identifier
+$id.
+
+=cut
+
+sub get_object {
+    my ($self, $otype, $id) = @_;
+
+    # finding perl class:
+    my $pclass = $self->_load_obj_class($otype) or return;
+    my $newobj = "$pclass"->new($self, $id);
+
+    defined($newobj) or do {
+        $self->log(LA_DEBUG, "$pclass->new() returned undef for $otype / %s", $id || '(none)');
+        return;
+    };
+
+    $newobj->{_base} = $self;
+    $newobj->{_type} = lc($otype);
+    $newobj->{_id} ||= $id;
+
+    return $newobj;
+}
+
+=head2 create_c_object($type, $id, %data)
+
+Create and return an object of type $type with unique id
+$id having %data using canonical fields
+
+=cut
+
+sub create_c_object {
+    my ($self, $otype, $id, %cdata) = @_;
+    $self->check_acl($otype, '@CREATE', 'w') or do {
+        $self->log(LA_WARN, 'permission denied to create object type %s',
+            $otype);
+        return;
+    };
+
+    $self->_create_c_object($otype, $id, %cdata);
+}
+
+sub _create_c_object {
+    my ($self, $otype, $id, %cdata) = @_;
+
+    $id ||= ''; # Avoid undef
+
+    if (my $chk = (
+        lc($otype) eq 'user' || lc($otype) eq 'group')
+        ? LATMOS::Accounts::Utils::check_ug_validity($id)
+        : LATMOS::Accounts::Utils::check_oid_validity($id)) {
+        $self->log(LA_ERR, "Cannot create $otype with ID $id `%s:'", $chk);
+        return;
+    }
+    foreach my $cfield (keys %cdata) {
+        $self->check_allowed_values($otype, $cfield, $cdata{$cfield}) or do {
+            my $last = LATMOS::Accounts::Log::lastmessage(LA_ERR);
+            $self->log(LA_ERR, "Cannot create $otype, wrong value%s", ($last ? ": $last" : ''));
+            return;
+        };
+    }
+
+    # populating default value
+    {
+        my %default = $self->compute_default($otype, $id, %cdata);
+        foreach my $k (keys %default) {
+            $cdata{$k} = $default{$k} unless(exists($cdata{$k}));
+        }
+    }
+
+    my %data;
+    my $sub;
+    foreach my $cfield (keys %cdata) {
+        # Parsing subobject creation:
+
+        if (my ($sotype, $key, $scfield) = $cfield =~ /^(\w+)(?:\[(\w+)\])?\.(.*)/) {
+            $key ||= '_';
+            $sub->{$sotype}{$key}{$scfield} = $cdata{$cfield};
+        } else {
+            my $attribute = $self->attribute($otype, $cfield) or next;
+            $attribute->ro and next;
+            $data{$cfield} = $cdata{$cfield};
+        }
+    }
+
+    $self->create_object($otype, $id, %data) or return;
+    my $obj = $self->get_object($otype, $id) or return;
+    $obj->ReportChange('Create', 'Object created with %s', join(', ', sort keys %cdata));
+
+    foreach my $attrname (keys %data) {
+        my $attribute = $self->attribute($obj->type, $attrname) or next;
+        $attribute->monitored or next;
+
+        $obj->ReportChange('Attributes', '%s set to %s', $attrname,
+            (ref $data{$attrname}
+                ? join(', ', @{ $data{$attrname} })
+                : $data{$attrname}) || '(none)');
+    }
+
+    if ($sub) {
+
+        # Security: if caller is create_c_object calling it to check permission ?
+        # See below
+        # my ($caller) = caller();
+        # my $subcreate = $caller eq 'create_c_object' ? 'create_c_object' : __SUB__;
+        
+        # Trying to create subobject
+        foreach my $sotype (keys %$sub) {
+            my $SubKeyRef = $self->GetSubObjectKey($otype, $sotype) or do {
+                $self->log(LA_ERR, "Cannot create object type $sotype, subtype of $otype not defined");
+                return;
+            };
+            foreach my $skey (keys %{ $sub->{$sotype} || {} }) {
+                # Building id
+                my $info = $sub->{$sotype}{$skey} || {};
+                # For id: if key is given using it, otherwise using random
+                my $sid = 
+                    $info->{$SubKeyRef} ||
+                    $id . '-' . join('', map { ('a' .. 'z')[rand(26)] } (0 .. 6));
+                $info->{$SubKeyRef} = $id;
+
+                # Here we don't check permission to create sub object:
+                $self->_create_c_object($sotype, $sid, %{ $info || {} }) or return;
+            }
+        }
+    }
+
+    $obj
+}
+
+=head2 create_object($type, $id, %data)
+
+Create and return an object of type $type with unique id
+$id having %data.
+
+This method should be provided by the data base handler.
+
+=cut
+
+sub create_object {
+    my ($self, $otype, $id, %data) = @_;
+
+    "$id" or do {
+        $self->log(LA_ERR, l("Cannot create %s object with empty id"),
+            $otype);
+        return;
+    };
+    my $pclass = $self->_load_obj_class($otype) or do {
+        $self->log(LA_ERR, "Cannot create %s object type (cannot load class)",
+            $otype);
+        return;
+    };
+
+    if (!$pclass->checkValues($self, $id, %data)) {
+        my $last = LATMOS::Accounts::Log::lastmessage(LA_ERR);
+        la_log(LA_ERR,
+            'Cannot create %s (%s) in base %s (%s): wrong value%s',
+            $id, $otype, $self->label, $self->type,
+            ($last ? ": $last" : ''),
+        );
+        return;
+    }
+
+    if ($pclass->_create($self, $id, %data)) {
+        la_log(LA_INFO,
+            'Object %s (%s) created in base %s (%s)',
+            $id, $otype, $self->label, $self->type,
+        );
+    } else {
+        my $last = LATMOS::Accounts::Log::lastmessage(LA_ERR);
+        la_log(LA_ERR,
+            'Object creation %s (%s) in base %s (%s) failed%s',
+            $id, $otype, $self->label, $self->type,
+            ($last ? ": $last" : ''),
+        );
+        return;
+    };
+
+    $self->get_object($otype, $id);
+}
+
+=head2 defaultAttributeValue($otype, $attr)
+
+Return default static value for attribute C<$attr>
+
+=cut
+
+sub defaultAttributeValue {
+    my ($self, $otype, $attr) = @_;
+
+    my %def = %{ $self->{defattr} || {}};
+    return $def{"$otype.$attr"} || '';
+}
+
+=head2 compute_default($otype, $id, %cdata)
+
+Return a hash containing value to set for new object
+
+=cut
+
+sub compute_default {
+    my ($self, $otype, $id, %cdata) = @_;
+
+    my %default;
+    foreach my $def (keys %{ $self->{defattr} || {}}) {
+        if ($def =~ /^$otype\.(.*)$/) {
+            $default{$1} = $self->{defattr}{$def} if(!$cdata{$1});
+        }
+    }
+
+    # computed default value (not a simple set)
+    if (lc($otype) eq 'user') {
+        if (!$cdata{homeDirectory}) {
+            $default{homeDirectory} = $self->{defattr}{'user.homebase'}
+                ? $self->{defattr}{'user.homebase'} . "/$id" 
+                : '';
+        }
+
+        if (!$cdata{uidNumber}) {
+            $default{uidNumber} ||= $self->find_next_numeric_id('user', 'uidNumber',
+            $self->{defattr}{'user.min_uid'}, $self->{defattr}{'user.max_uid'});
+        }
+
+        my $mailid = $cdata{givenName} && $cdata{sn}
+            ? sprintf('%s.%s',
+                to_ascii(lc($cdata{givenName})),
+                to_ascii(lc($cdata{sn})),)
+            : undef;
+        $mailid =~ s/\s+/-/g if($mailid);
+
+        if ($mailid &&
+            $self->is_supported_object('aliases') &&
+            ! $self->get_object('aliases', $mailid)) {
+            if (my $attr = $self->attribute($otype, 'mail')) {
+                if ((!$attr->ro) && $self->{defattr}{'user.maildomain'}) {
+                    $default{mail} ||= sprintf('%s@%s',
+                    $mailid,
+                    $self->{defattr}{'user.maildomain'});
+                }
+            }
+            if (my $attr = $self->attribute($otype, 'aliases')) {
+                $default{aliases} ||= $mailid unless ($attr->ro);
+            }
+            if (my $attr = $self->attribute($otype, 'revaliases')) {
+                $default{revaliases} ||= $mailid unless ($attr->ro);
+            }
+        }
+    } elsif (lc($otype) eq 'group') {
+        if (!$cdata{gidNumber}) {
+            $default{gidNumber} ||= $self->find_next_numeric_id(
+                'group', 'gidNumber',
+                $self->{defattr}{'group.min_gid'},
+                $self->{defattr}{'group.max_gid'}
+            );
+        }
+    }
+
+    return %default;
+}
+
+sub _allowed_values {
+    $_[0]->{_allowed_values}
+}
+
+=head2 obj_attr_allowed_values ($otype, $attr)
+
+Return value allowed for this attribute
+
+=cut
+
+sub obj_attr_allowed_values {
+    my ($self, $otype, $attr) = @_;
+    if ($self->_allowed_values &&
+	$self->_allowed_values->SectionExists("$otype.$attr")) {
+        return grep { defined($_) } $self->_allowed_values->val("$otype.$attr", 'allowed');
+    }
+    return();
+}
+
+=head2 check_allowed_values ($otype, $attr, $attrvalues)
+
+Check attributes C<$attr> of object type C<$otype> allow values C<$attrvalues>
+
+=cut
+
+sub check_allowed_values {
+    my ($self, $otype, $attr, $attrvalues) = @_;
+    $self->_allowed_values or return 1;
+    my @values = ref $attrvalues ? @{ $attrvalues } : $attrvalues;
+    foreach my $value (@values) {
+        $value or next;
+        if (my @allowed = $self->obj_attr_allowed_values($otype, $attr)) {
+            grep { $value eq $_ } @allowed or do {
+                $self->log(LA_ERR,
+                    "value `%s' is not allow for %s.%s per configuration (allowed_values)",
+                    $value, $otype, $attr
+                );
+                return;
+            };
+        }
+    }
+    return 1;
+}
+
+=head2 list_canonical_fields($otype, $for)
+
+Return the list of supported fields by the database for object type $otype.
+
+Optionnal $for specify the goal for which the list is requested, only supported
+fields will be returns
+
+=cut
+
+sub list_canonical_fields {
+    my ($self, $otype, $for) = @_;
+    $for ||= 'rw';
+    my $pclass = $self->_load_obj_class($otype) or return;
+    sort $pclass->_canonical_fields($self, $for);
+}
+
+sub _get_attr_schema {
+    my ($self, $otype) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    return $pclass->_get_attr_schema($self);
+}
+
+=head2 get_attr_schema
+
+Deprecated
+
+=cut
+
+# TODO: kill this
+
+sub get_attr_schema {
+    my ($self, $otype, $attribute) = @_;
+    my $info = $self->_get_attr_schema($otype);
+    if ($info->{$attribute}) {
+        return $info->{$attribute};
+    } else {
+        return;
+    }
+}
+
+=head2 attribute($otype, $attribute)
+
+Return attribute object.
+
+See L<LATMOS::Accounts::Bases::Attribute>
+
+=cut
+
+sub attribute {
+    my ($self, $otype, $attribute) = @_;
+
+    my $attrinfo;
+    if (!ref $attribute) {
+       $attrinfo = $self->get_attr_schema($otype, $attribute)
+            or return;
+        $attrinfo->{name} = $attribute;
+    } else {
+        $attrinfo = $attribute;
+    }
+
+    return LATMOS::Accounts::Bases::Attributes->new(
+        $attrinfo,
+        $self,
+        $otype,
+    );
+}
+
+=head2 delayed_fields
+
+DEPRECATED
+
+=cut 
+
+# TODO: kill this
+
+sub delayed_fields {
+    my ($self, $otype, $for) = @_;
+    $self->log(LA_WARN, "calling DEPRECATED delayed_fields " . join(',',
+            caller));
+    $for ||= 'rw';
+    my @attrs;
+    foreach ($self->list_canonical_fields($otype, $for)) {
+        my $attr = $self->attribute($otype, $_) or next;
+        $for =~ /w/ && $attr->ro and next;
+        $attr->delayed or next;
+        push(@attrs, $_);
+    }
+    @attrs
+}
+
+=head2 ochelper ($otype)
+
+Return L<LATMOS::Accounts::Bases::OChelper> object
+
+=cut
+
+sub ochelper {
+    my ($self, $otype) = @_;
+    my $pclass = ucfirst(lc($otype));
+    foreach my $class (
+        ref($self) . '::OCHelper::' . $pclass,
+        ref($self) . '::OCHelper',
+        "LATMOS::Accounts::Bases::OCHelper::$pclass",
+        'LATMOS::Accounts::Bases::OCHelper' ) {
+        eval "require $class;";
+        if ($@) { next } # error message ?
+        my $ochelper = "$class"->new($self, $otype);
+        return $ochelper;
+    }
+    return;
+}
+
+=head2 delete_object($otype, $id)
+
+Destroy from data base object type $otype having id $id.
+
+=cut
+
+sub delete_object {
+    my ($self, $otype, $id) = @_;
+    my $obj = $self->get_object($otype, $id) or do {
+        $self->log(LA_WARN, 'Cannot delete %s/%s: no such object',
+            $otype, $id);
+        return;
+    };
+    $self->check_acl($obj, '@DELETE', 'w') or do {
+        $self->log(LA_WARN, 'permission denied to delete %s/%s',
+            $otype, $id);
+        return;
+    };
+    my $ref = $obj->Iid;
+    if (my $res = $self->_delete_object($otype, $id)) {
+        $self->ReportChange($otype, $id, $ref, 'Delete', 'Object deleted');
+        return $res;
+    }
+    return;
+}
+
+sub _delete_object {
+    my ($self, $otype, $id) = @_;
+    my $pclass = $self->_load_obj_class($otype);
+    $pclass->_delete($self, $id);
+}
+
+=head2 rename_object($otype, $id, $newid)
+
+Rename an object.
+
+=cut
+
+sub rename_object {
+    my ($self, $otype, $id, $newid) = @_;
+
+    $self->log(LA_DEBUG, "Trying to rename $otype/$id to $newid");
+    my $obj = $self->get_object($otype, $id) or do {
+        $self->log(LA_WARN, 'Cannot rename %s/%s: no such object',
+            $otype, $id);
+        return;
+    };
+    if (my $chk = (lc($otype) eq 'user' || lc($otype) eq 'group')
+        ? LATMOS::Accounts::Utils::check_ug_validity($newid)
+        : LATMOS::Accounts::Utils::check_oid_validity($newid)) {
+        $self->log(LA_ERR, "Cannot rename $otype/$id to ID $newid `%s:'", $chk);
+        return;
+    }
+    $self->check_acl($obj, '@DELETE', 'w') &&
+    $self->check_acl($obj, '@CREATE', 'w') or do {
+        $self->log(LA_WARN, 'permission denied to rename %s/%s',
+            $otype, $id);
+        return;
+    };
+
+    my $oldref = $obj->Iid;
+
+    if (my $res = $self->_rename_object($otype, $id, $newid)) {
+        my $newobj = $self->get_object($otype, $newid) or do {
+            $self->log(LA_WARN, 'Cannot get object %s/%s: rename failed ?',
+                $otype, $id);
+            return;
+        };
+
+        $self->ReportChange($otype, $id, $oldref, 'Rename', 'Object rename to %s', $newid);
+        $newobj->ReportChange('Rename', 'Object renamed from %s', $id);
+        return $res;
+    }
+    return;
+}
+
+sub _rename_object {
+    my ($self, $otype, $id, $newid) = @_;
+    my $pclass = $self->_load_obj_class($otype);
+    $pclass->can('_rename') or do {
+        $self->log(LA_ERR, 'rename object type %s is unsupported', $otype);
+        return;
+    };
+    $pclass->_rename($self, $id, $newid);
+}
+
+=head2 is_transactionnal
+
+Return True is the database support commit and rollback
+
+=cut
+
+sub is_transactionnal {
+    my ($self) = @_;
+    return($self->can('_rollback') && $self->can('_commit'));
+}
+
+=head2 commit
+
+Save change into the database if change are not done immediately.
+This should always be called as you don't know when change are applied.
+
+Return always true if database does not support any transaction.
+
+The driver should provides a _commit functions to save data.
+
+=cut
+
+sub commit {
+    my ($self) = @_;
+    if ($self->can('_commit')) {
+        la_log(LA_DEBUG, 'Commiting data');
+        if (!(my $res = $self->_commit)) {
+	    la_log(LA_ERR, "Commit error on %s", $_->label);
+            return $res;
+        }
+    }
+
+    $self->postcommit();
+
+    return 1;
+}
+
+=head2 postcommit
+
+Run postcommit command
+
+=cut
+
+sub postcommit {
+    my ($self) = @_;
+
+    if ($self->{_options}{postcommit}) {
+        exec_command($self->{_options}{postcommit},
+            {
+                BASE => $self->label,
+                BASETYPE => $self->type,
+                HOOK_TYPE => 'POST',
+                CONFIG => $self->{_options}{configdir},  
+            }
+        );
+    } else {
+        return 1;
+    }
+}
+
+=head2 rollback
+
+If database support transaction, rollback changes. Return false
+if database does not support.
+
+If supported, driver should provides a _rollback functions
+
+=cut
+
+sub rollback {
+    my ($self) = @_;
+    if ($self->can('_rollback')) {
+       la_log(LA_DEBUG, 'Rolling back data');
+       return $self->_rollback;
+   } else {
+       return 0;
+   }
+}
+
+=head2 current_rev
+
+Return the current revision of the database
+
+Must be provide by base driver if incremental synchro is supported
+
+=cut
+
+sub current_rev { return }
+
+=head2 list_objects_from_rev($otype, $rev)
+
+Return the list of UID for object of $otype.
+
+=cut
+
+sub list_objects_from_rev {
+    my ($self, $otype, $rev) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    if (defined($rev) && $pclass->can('list_from_rev')) {
+        return $pclass->list_from_rev($self, $rev);
+    } else {
+        # no support, return all objects...
+        return $self->list_objects($otype);
+    }
+}
+
+=head2 sync_object_from($srcbase, $otype, $id, %options)
+
+Sync object type C<$otype> C<$id> from base C<$srcbase> to current base.
+
+C<%options>:
+
+=over 4
+
+=item nodelete
+
+Don't delete object if the object synchronize don't exist in source base
+
+=back
+
+=cut
+
+sub sync_object_from {
+    my ($self, $srcbase, $otype, $id, %options) = @_;
+
+    # is the object type supported by both
+    foreach ($self, $srcbase) {
+        $_->is_supported_object($otype) or return '';
+    }
+    
+    if (my $srcobj = $srcbase->get_object($otype, $id)) {
+        return $self->sync_object($srcobj, %options);
+    } elsif (!$options{nodelete}) {
+        $self->_delete_object($otype, $id) and return 'DELETED';
+    }
+    return;
+}
+
+=head2 sync_object
+
+Synchronise an object into this base
+
+=cut
+
+sub sync_object {
+    my ($self, $srcobj, %options) = @_;
+    $self->is_supported_object($srcobj->type) or return '';
+    my @fields = $options{attrs}
+        ? @{ $options{attrs} }
+        : $self->list_canonical_fields($srcobj->type, 'w');
+    my %data;
+    foreach (@fields) {
+	# check attribute exists in source:
+        my $attr = $srcobj->attribute($_) or next;
+        $attr->readable or next;
+        if (! $options{onepass}) {
+            if ($options{firstpass}) {
+                $attr->delayed and next;
+            } else {
+                $attr->delayed or next;
+            }
+        }
+        $data{$_} = $srcobj->GetAttributeValue($_);
+    }
+    if (my $dstobj = $self->get_object($srcobj->type, $srcobj->id)) {
+        keys %data or return 'SYNCED';
+        foreach (keys %data) {
+            if (!$dstobj->attribute($_) ||
+                $dstobj->attribute($_)->ro) {
+                delete($data{$_});
+            }
+        }
+        my $res = $dstobj->_set_c_fields(%data);
+        if (defined $res) {
+            return $res ? 'SYNCED' : '';
+        } else {
+            return;
+        }
+    } elsif(!$options{nocreate}) {
+        if ((! $options{firstpass}) && (!$options{onepass})) {
+            $self->log(LA_ERR, 'This is not first pass, creation wanted but denied');
+            return;
+        }
+        if (my $res = $self->_create_c_object($srcobj->type, $srcobj->id, %data)) {
+            return 'CREATED'
+        } else {
+            return;
+        }
+    } else {
+	# No error, but creation is denied
+        return 'Creation skipped';
+    }
+
+    return;
+}
+
+=head2 search_objects($otype, @filter)
+
+Search object according @filter. @filter is a list
+of field/value which should match.
+
+A default function is provided but each db driver can provide
+an optimize version.
+
+=cut
+
+sub search_objects {
+    my ($self, $otype, @filter) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    $pclass->search($self, @filter);
+}
+
+=head2 attributes_summary($otype, $attr)
+
+Return couple object id / value for attribute C<$attr> of object type C<$otype>
+
+This method is designed to be faster than fetching object one by one.
+
+=cut
+
+sub attributes_summary {
+    my ($self, $otype, $attr) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    $pclass->attributes_summary($self, $attr);
+}
+
+=head2 attributes_summary_by_object($otype, $attr)
+
+Return couple object id / value for attribute C<$attr> of object type C<$otype>
+
+This method is designed to be faster than fetching object one by one.
+
+=cut
+
+sub attributes_summary_by_object {
+    my ($self, $otype, $attr) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    $pclass->attributes_summary_by_object($self, $attr);
+}
+
+=head2 fetchObjectInfo($otype, $attributes, @filters)
+
+Return an hashref with attribute data for each object matching @filters.
+
+If C<$attributes> can be an array ref to a list of attributes.
+
+=cut
+
+sub fetchObjectInfo {
+    my ($self, $otype, $attributes, @filters) = @_;
+
+    my %results = map { $_ => {} }  $self->search_objects($otype, @filters);
+
+    foreach my $attr ( ref $attributes ? @$attributes : $attributes) {
+        my %res = $self->attributes_summary_by_object($otype, $attr);
+        foreach my $obj (keys %results) {
+            $results{$obj}{$attr} = $res{$obj};
+        }
+    }
+
+    \%results;
+}
+
+=head2 find_next_numeric_id($otype, $field, $min, $max)
+
+Return, if possible, next numeric id available (typically unix user UID).
+
+=cut
+
+sub find_next_numeric_id {
+    my ($self, $otype, $field, $min, $max) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    $pclass->find_next_numeric_id($self, $field, $min, $max);
+}
+
+=head2 authenticate_user($username, $passwd)
+
+Return true if authentication success.
+
+Must be override by driver if the base have a proper authentication method
+
+=cut
+
+sub authenticate_user {
+    my ($self, $username, $passwd) = @_;
+    $username or return;
+    my $uobj = $self->get_object('user', $username) or do {
+        la_log(LA_ERR, "Cannot authenticate non existing user $username");
+        return;
+    };
+
+    if ($uobj->_get_c_field('expired')) {
+        la_log(LA_ERR, "Account $username has expired (%s)",
+            $uobj->_get_c_field('expired'));
+        return;
+    }
+
+    if ($uobj->_get_c_field('locked')) {
+        la_log(LA_ERR, "Account $username is currently locked");
+        return;
+    }
+
+    my $password = $uobj->get_field('userPassword') or do {
+        la_log(LA_ERR, "Cannot authenticate user $username having no passwd");
+        return;
+    };
+    if ($password eq crypt($passwd, $password)) { # crypt unix
+        la_log(LA_NOTICE, "User $username authenticated");
+        return 1;
+    } else {
+        la_log(LA_ERR, "Cannot authenticate user $username: wrong password");
+        return 0;
+    }
+}
+
+=head2 passCrypt($clear_pass)
+
+Return an encrypted password using method set in config
+
+=cut
+
+sub passCrypt {
+    my ($self, $clear_pass) = @_;
+
+    my $method = $self->config('crypt_method');
+
+    LATMOS::Accounts::Utils::Crypt($clear_pass, $method);
+}
+
+=head2 connect($username, $password)
+
+Authenticate the user and store the username as connected
+
+=cut
+
+sub connect {
+    my ($self, $username, $password) = @_;
+    my $auth = $self->authenticate_user($username, $password);
+    if ($auth) {
+        $self->{_user} = $username;
+        la_log(LA_DEBUG, "Connected as $username");
+    }
+    return $auth;
+}
+
+=head2 user
+
+Return the current connected username
+
+=cut
+
+sub user { $_[0]->{_user} }
+
+=head2 check_acl($obj, $attr, $perm)
+
+Return true if connected user have C<$perm> permission on attribute C<$attr> of
+object C<$obj>.
+
+=cut
+
+sub check_acl {
+    my ($self, $obj, $attr, $perm) = @_;
+    if ($self->{_acls}) {
+        my ($who, $groups) = ($self->user || '');
+
+        $self->{_acl_cache}{uid} ||= '';
+
+        if ($self->{_acl_cache}{uid} ne $who) {
+            $self->{_acl_cache}{uid} = $who;
+            my $uo = $self->get_object('user', $who);
+            $self->{_acl_cache}{obj} = $uo;
+            if ($uo) {
+                $self->{_acl_cache}{groups} = [ $uo->_get_attributes('memberOf') ];
+            }
+        }
+
+        if ($who && (my $uo = $self->{_acl_cache}{obj})) {
+            $groups = $self->{_acl_cache}{groups};
+        } else {
+            $who = '';
+        }
+        my $res = $self->{_acls}->check($obj, $attr, $perm, $who, $groups);
+        $self->log(LA_INFO, 'permission denied for "%s" to get %s.%s for %s',
+           $who, ref $obj ? $obj->id . '(' . $obj->type . ')' : $obj, $attr, $perm) if (!$res);
+        return $res;
+    } else {
+        # No acls, woot
+        return 1;
+    }
+}
+
+=head2 text_empty_dump($fh, $otype, $options)
+
+Empty object dump
+
+=cut
+
+sub text_empty_dump {
+    my ($self, $fh, $otype, $options) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    $pclass->text_dump($fh, $options, $self);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.fr<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad.pm	(revision 2119)
@@ -0,0 +1,184 @@
+package LATMOS::Accounts::Bases::Ad;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Ldap);
+use Net::LDAP;
+use Net::LDAP::Entry;
+use Net::LDAP::Control::Paged;
+use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
+use Net::LDAP::Util     qw( escape_filter_value );
+use Unicode::Map8;
+use Unicode::String qw(utf16);
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ad - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Ad;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Ad, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 new(%config)
+
+Create a new LATMOS::Ad object for windows AD $domain.
+
+config:
+
+=over 4
+
+=item domain
+
+The Active directory domain
+
+=item server (optional)
+
+If set, try to connect to this server, if not set, a dns query is performed
+to find AD server, first responding is used.
+
+=item ssl
+
+If set, try to connect using ssl
+
+=item OBJECT_container
+
+The sub path where to find object type of OBJECT and where they should
+be created
+
+=back
+
+=cut
+
+sub new {
+    my ($class, %config) = @_;
+    
+    $config{domain} or do {
+        la_log(LA_ERR,
+            "Cannot instanciate base %s w/o domain name",
+            $config{label} || '(unknown label)'
+        );
+        return;
+    };
+
+    my $self = {
+        _top_dn => join(',', map { "dc=$_" } split('\.', $config{domain})),
+    };
+
+    bless($self, $class);
+}
+
+sub load {
+    my ($self) = @_;
+
+    # If already load, just say ok !
+    $self->{_ldap} and return 1;
+
+    # At this point, if still no $server, DNS search
+
+    my $ldap;
+    my @ldapservers = ($self->config('server')
+        ? ($self->_ldap_url($self->config('server')))
+        : ($self->_query_zone_ads)) or do {
+        la_log(LA_ERR,
+            "Cannot find any ldap server for domain %s", $self->ad_domain);
+        return;
+    };
+    foreach my $tryserv (@ldapservers) {
+        $self->log(LA_DEBUG, "Trying to connect to ldap %s", $tryserv);
+        $ldap = Net::LDAP->new(
+            $tryserv,
+        ) and last;
+    }
+ 
+    if($ldap) {
+        $self->log(LA_DEBUG, "Connect to ldap server done");
+    } else {
+        $self->log(LA_ERR, "Cannot connect to any ldap server");
+        return; # cannot connect to any ldap :\
+    };
+    
+    if ($self->config('tls')) {
+        $self->log(LA_DEBUG, "Running start_tls()");
+        $ldap->start_tls(
+            verify => 'none',
+        );
+    }
+
+    my $login = $self->config('login');
+    $login =~ m/@/ or $login .= '@' . $self->ad_domain;
+
+    my $msg = $ldap->bind($login, password => $self->config('password'));
+    $msg->code and do {
+        $self->log(LA_ERR, "Cannot bind ldap: %s", $msg->error);
+        return;
+    };
+
+    $self->{_ldap} = $ldap;
+    return 1;
+}
+
+sub _query_zone_ads {
+    my ($self) = @_;
+    require Net::DNS;
+    my @urllist;
+
+    my $resolver = Net::DNS::Resolver->new;
+    my $query = $resolver->query("_ldap._tcp.dc._msdcs." . $self->ad_domain,
+        "SRV") or return;
+    foreach my $rr (
+        sort { $a->priority <=> $b->priority || $a->weight <=> $b->weight }
+        grep { $_->type eq 'SRV' } $query->answer) {
+        push(@urllist, $self->_ldap_url($rr->target)); # $rr->port)); don't use port
+    }
+
+    @urllist
+}
+
+=head2 ad_domain
+
+Return the active directory zone
+
+=cut
+
+sub ad_domain {
+    return $_[0]->config('domain')
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad/Group.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad/Group.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad/Group.pm	(revision 2119)
@@ -0,0 +1,222 @@
+package LATMOS::Accounts::Bases::Ad::Group;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Ad::objects);
+use Net::LDAP;
+use Net::LDAP::Entry;
+use Net::LDAP::Control::Paged;
+use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
+use Net::LDAP::Util     qw( escape_filter_value );
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ad - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Ad;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Ad, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _class_filter { '(ObjectClass=group)' }
+
+sub _key_attr { 'cn' } 
+
+sub _my_ldap_classes { qw(top group) }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+    my $info = {
+        gidNumber  => { uniq => 1, },
+        description => { },
+        member => {
+            delayed => 1,
+            can_values => sub { $base->list_objects('user') },
+            multiple => 1,
+        },
+        memberUID => {
+            delayed => 1,
+            multiple => 1,
+        },
+        sAMAccountName => { },
+        managedBy => {
+            delayed => 1,
+            can_values => sub { $base->list_objects('user') },
+        },
+        cn => { ro => 1, },
+        dn => { ro => 1, },
+        objectClass => {
+            ro => 1,
+            multiple => 1,
+        },
+        msSFU30NisDomain => { },
+        msSFU30Name => { },
+    };
+
+    $info->{$class->_key_attribute($base)}{ro} = 1;
+    $info->{$class->_dn_attribute($base)}{ro} = 1;
+
+    $info
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+
+    $data{sAMAccountName} = 'GR-' . $id;
+    my $entry = Net::LDAP::Entry->new();
+
+    $entry->dn(join(',',
+        sprintf('%s=%s',
+            $class->_dn_attribute($base),
+            escape_filter_value($id)),
+            $base->object_base_dn($class->type
+        ),
+    ));
+    $entry->replace(objectClass => [ $class->_my_ldap_classes ],);
+    $data{$class->_key_attribute($base)} = $id;
+    my %delayed;
+    foreach (keys %data) {
+        /^(memberUID|member)$/ and do {
+            $delayed{$_} = $data{$_};
+            next;
+        };
+        $class->_populate_entry($entry, $_, $data{$_}, $base);
+    }
+    my $msg = $base->ldap->add($entry);
+    $base->log(LA_ERR, "Cannot create group: %s", $msg->error) if ($msg->code);
+    return if ($msg->code);
+    if (! keys %delayed) { return 1 };
+    my $res = $base->get_object('group', $id)->set_fields(%delayed);
+    return defined($res) ? 1 : 0;
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+    require LATMOS::Accounts::Bases::Ad::User;
+
+    $field eq 'member' and do {
+        my @res;
+        $self->base->_unlimited_search(
+            base => $self->base->object_base_dn('user'),
+            filter => sprintf(
+                '(&(objectClass=user)(memberOf=%s))',
+                escape_filter_value($self->{entry}->dn),
+            ),
+            callback => sub {
+                my ($mesg, $entry) = @_;
+                ref $entry eq 'Net::LDAP::Entry' or return;
+                push(@res, $entry->get_value(
+                        LATMOS::Accounts::Bases::Ad::User->_key_attribute($self->base)
+                    )
+                );
+            },
+        );
+        return [ sort(@res) ];
+    };
+    $field eq 'memberUID' and do {
+        my $val = $self->SUPER::get_field($field);
+        return ref $val ? $val : [ grep { $_ } $val ];
+    };
+    $self->SUPER::get_field($field);
+}
+
+sub _populate_entry {
+    my ($self, $entry, $field, $value, $base) = @_;
+    $base ||= $self->base;
+    for ($field) {
+        /managedBy$/ and do {
+            if ($value && (my $user = $base->get_object('user', $value))) {
+                $value = $user->get_field('dn');
+            } else {
+                $value = undef;
+            }
+            next;
+        };
+        /^sAMAccountName$/ and do {
+            # Hack... to avoid clash with user
+            if ($value !~ /^GR-/) {
+                $value = 'GR-' . $value;
+            }
+	    next;
+        };
+    }
+    $self->SUPER::_populate_entry($entry, $field, $value, $base);
+}
+
+sub set_fields {
+    my ($self, %data) = @_;
+    my %ndata;
+    while (my ($f, $val) = each(%data)) {
+        $f eq 'memberUID' and do {
+            $val = [ $val ] unless(ref $val);
+            my %users;
+            $users{$_}{e} = 1 foreach (grep { defined($_) } @{ $self->get_field('memberUID') || []});
+            $users{$_}{n} = 1 foreach (grep { defined($_) } @{ $val || []});
+            foreach (keys %users) {
+                $users{$_}{e} && $users{$_}{n} and next;
+                if ($users{$_}{e}) {
+                    $self->{entry}->delete(memberUID => $_);
+                } elsif ($users{$_}{n} && $self->base->get_object('user', $_)) {
+                    $self->{entry}->add(memberUID => $_);
+                } # else {} # can't happen
+            }
+            next;
+        };
+        $f eq 'member' and do {
+            my %users;
+            $val = [ $val ] unless(ref $val);
+            $users{$_}{e} = 1 foreach (grep { defined($_) } @{ $self->get_field('member') || []});
+            $users{$_}{n} = 1 foreach (grep { defined($_) } @{ $val || []});
+            foreach (keys %users) {
+                $users{$_}{e} && $users{$_}{n} and next;
+                my $user = $self->base->get_object('user', $_) or next;
+                if ($users{$_}{e}) {
+                    $self->{entry}->delete(member => $user->get_field('dn'));
+                } elsif ($users{$_}{n}) {
+                    $self->{entry}->add(member => $user->get_field('dn'));
+                } # else {} # can't happen
+            }
+            next;
+        };
+        $ndata{$f} = $val;
+    }
+    $self->SUPER::set_fields(%ndata);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad/User.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad/User.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad/User.pm	(revision 2119)
@@ -0,0 +1,350 @@
+package LATMOS::Accounts::Bases::Ad::User;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Ad::objects);
+use Net::LDAP;
+use Net::LDAPS;
+use Net::LDAP::Entry;
+use Net::LDAP::Control::Paged;
+use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
+use Net::LDAP::Util     qw( escape_filter_value );
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ad - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Ad;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Ad, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _class_filter { '(&(ObjectClass=user) (!(ObjectClass=computer)))' }
+
+sub _key_attr { 'cn' } 
+
+sub _my_ldap_classes { qw(top person organizationalPerson user) }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+    my $info = {
+        sn => { },
+        name => { },
+        givenName => { },
+        sAMAccountName => { },
+        uid => { uniq => 1, },
+        gecos => { },
+        homeDirectory => { },
+        loginShell => { },
+        uidNumber => { uniq => 1, },
+        gidNumber => {
+            mandatory => 1,
+            can_values => sub {
+                map { $base->get_object('group',
+                        $_)->get_attributes('gidNumber') }
+                $base->list_objects('group')
+            },
+            display => sub {
+                my ($self, $val) = @_;
+                my ($gr) = $self->base->search_objects('group', "gidNumber=$val")
+                    or return;
+                return $gr;
+            },
+            reference => 'group',
+        },
+        shadowLastChange => { },
+        shadowMin => { },
+        shadowMax => { },
+        shadowWarning => { },
+        shadowInactive => { },
+        shadowExpire => { },
+        shadowFlag => { },
+        description => { },
+        mail => { },
+        telephoneNumber => { },
+        ipPhone => { },
+        otherTelephone => { },
+        department => { },
+        title => { },
+        mobile => { },
+        homePhone => { },
+        accountExpires => { },
+        streetAddress => { },
+        postalCode => { },
+        postOfficeBox => { },
+        l => { },
+        physicalDeliveryOfficeName => { },
+        company => { },
+        st => { },
+        displayName => { },
+        initials => { },
+        manager => {
+            delayed => 1,
+            can_values => sub { $base->list_objects('user') },
+        },
+        userAccountControl => { },
+        locked => { },
+        memberOf => {
+            multiple => 1,
+            delayed => 1,
+        },
+        winhomeDirectory => { },
+        facsimileTelephoneNumber => { },
+        cn => { ro => 1, },
+        dn => { ro => 1, },
+        uSNCreated => { ro => 1, },
+        uSNChanged => { ro => 1, },
+        directReports => { ro => 1, },
+        objectClass => { ro => 1, },
+        msSFU30NisDomain => { },
+        msSFU30Name => { },
+        labeledURI => {},
+        wWWHomePage => {},
+        userPassword => { readable => 0 },
+    };
+
+    $info->{$class->_key_attribute($base)}{ro} = 1;
+    $info->{$class->_dn_attribute($base)}{ro} = 1;
+
+    $info
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+
+    my $entry = Net::LDAP::Entry->new();
+
+    $entry->dn(join(',',
+        sprintf('%s=%s',
+            $class->_dn_attribute($base), 
+            escape_filter_value($id)),
+            $base->object_base_dn($class->type),
+    ));
+    $data{'sAMAccountName'} ||= substr($id, 0, 19);
+    $entry->replace(objectClass => [ $class->_my_ldap_classes ],);
+    # Must be 544 for creation...
+    $entry->replace(userAccountControl => 544); #66112);
+    # This attributes cannot be set via LDAP:
+    #$entry->replace(sAMAccountType => 0x30000000);
+    $entry->replace(accountExpires => '9223372036854775807'); # TODO hardcoded, burk
+    $entry->replace(userPrincipalName => "$id\@" . $base->ad_domain);
+
+    {
+        my $charmap = Unicode::Map8->new('latin1')  or  die;
+        my $clear_pass = LATMOS::Accounts::Utils::genpassword(lenght => 20, nonalpha => 1);
+        my $newUniPW = $charmap->tou('"'.$clear_pass.'"')->byteswap()->utf16();
+        $entry->replace(unicodePwd => $newUniPW);
+    }
+
+    my %delayed;
+    $data{$class->_key_attribute($base)} = $id;
+    foreach (keys %data) {
+	/^memberOf$/ and do {
+	    $delayed{$_} = $data{$_};
+	    next;
+        };
+        $class->_populate_entry($entry, $_, $data{$_}, $base);
+    }
+    my $msg = $base->ldap->add($entry);
+    if ($msg->code) {
+    	$base->log(LA_ERR, "Cannot create user: %s", $msg->error);
+    	return 0;
+    }
+    if (my $obj = $base->get_object('user', $id)) {
+        my $res = $obj->set_fields(%delayed);
+        return defined($res) ? 1 : 0;
+    } else {
+        $base->log(LA_ERR, "Cannot get just created object %s/%s", 'user', $id);
+        return 0;
+    }
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+    require LATMOS::Accounts::Bases::Ad::Group;
+
+    $field eq 'memberOf' and do {
+        my @res;
+        $self->base->_unlimited_search(
+            base => $self->base->object_base_dn('group'),
+            filter => sprintf(
+                '(&(objectClass=group)(member=%s))',
+                escape_filter_value($self->{entry}->dn),
+            ),
+            callback => sub {
+                my ($mesg, $entry) = @_;
+                ref $entry eq 'Net::LDAP::Entry' or return;
+                push(@res, $entry->get_value(
+                        LATMOS::Accounts::Bases::Ad::Group->_key_attribute($self->base)
+                    )
+                );
+            },
+        );
+        return [ sort(@res) ];
+    };
+    $field eq 'directReports' and do {
+        my $res = $self->SUPER::get_field($field) or return;
+        return [
+            map { $self->base->_get_object_from_dn($_)->get_value(
+                LATMOS::Accounts::Bases::Ad::User->_key_attribute($self->base)
+            ) }
+            @{ ref $res ? $res : [ $res ] }
+        ];
+    };
+    $field eq 'manager' and do {
+        my $dn = $self->SUPER::get_field($field) or return;
+        return $self->base->_get_object_from_dn($dn)->get_value(
+            LATMOS::Accounts::Bases::Ad::User->_key_attribute($self->base)
+        );
+    };
+    $field eq 'homeDirectory' and do {
+        $field = 'unixHomeDirectory';
+    };
+    $field eq 'winhomeDirectory' and do {
+        $field = 'homeDirectory';
+    };
+    $self->SUPER::get_field($field);
+}
+
+sub _populate_entry {
+    my ($self, $entry, $field, $value, $base) = @_;
+    $base ||= $self->base;
+    # MS Ad sucks, does not respect RFC
+    # homeDirectory => unixHomeDirectory
+    # Win home => homeDirectory...
+    # The question is now: do we have to manage Ad homeDirectory field ?
+    for ($field) {
+        /^homeDirectory$/ and do {
+            $field = 'unixHomeDirectory';
+            next;
+        };
+        /^winhomeDirectory$/ and do {
+            $field = 'homeDirectory';
+            next;
+        };
+        /^manager$/ && $value and do {
+            my $user = $base->get_object('user', $value) or do {
+                $value = undef;
+                next;
+            };
+            $value = $user->get_field('dn');
+            next;
+        };
+        /^locked$/ and do {
+            my $uac = $entry->get_value('userAccountControl') || 0;
+            if ($value) {
+                $uac |= 0x00000002;
+            } else {
+                $uac &= (0xFFFFFFFF ^ 0x00000002);
+            }
+            $self->SUPER::_populate_entry($entry, 'userAccountControl', "$uac",
+                $base);
+            return; # nothing more to do
+        };
+        /^gecos$/ and do {
+            $value = to_ascii($value);
+        };
+    }
+    $self->SUPER::_populate_entry($entry, $field, $value, $base);
+}
+
+sub set_fields {
+    my ($self, %data) = @_;
+    my %ndata;
+    while (my ($f, $val) = each(%data)) {
+        $f eq 'memberOf' and do {
+            $val = [ $val ] unless(ref $val);
+            my %users;
+            $users{$_}{e} = 1 foreach (grep { $_ } @{ $self->get_field('memberOf') || []});
+            $users{$_}{n} = 1 foreach (grep { $_ } @{ $val || []});
+            foreach (keys %users) {
+                $users{$_}{e} && $users{$_}{n} and next;
+                my $group = $self->base->get_object('group', $_) or next;
+                # memberOf field is read only
+                # so we modify the peer entry into peer object
+                if ($users{$_}{e}) {
+                    $group->{entry}->delete(member => $self->get_field('dn'));
+                } elsif ($users{$_}{n}) {
+                    $group->{entry}->add(member => $self->get_field('dn'));
+                } # else {} # can't happen
+                my $mesg = $group->{entry}->update($self->base->ldap);
+                if ($mesg->code) {
+                    $self->base->log(LA_ERR, "Cannot set attributes: %s", $mesg->error);
+                    return;
+                }
+            }
+            next;
+        };
+        $ndata{$f} = $val;
+    }
+    $self->SUPER::set_fields(%ndata);
+}
+
+sub _set_password {
+    my ($self, $clear_pass) = @_;
+    my $charmap = Unicode::Map8->new('latin1')  or  die;
+    my $newUniPW = $charmap->tou('"'.$clear_pass.'"')->byteswap()->utf16();
+    my $mesg = $self->base->ldap->modify(
+        $self->_get_c_field('dn'),
+        changes => [
+        #replace => [ userPassword => $clear_pass ],
+        replace => [ unicodePwd => $newUniPW ],
+
+        ]
+    );
+    if ($mesg->code) {
+        $self->base->log(LA_ERR, "Cannot set password: %s", $mesg->error);
+        return;
+    }
+
+    my $userAccountControl = $self->get_field('userAccountControl');
+    # http://msdn.microsoft.com/en-us/library/ms680832(VS.85).aspx
+    $userAccountControl |= 0x00010000; # ADS_UF_DONT_EXPIRE_PASSWD
+                                       # The password for this account will never expire.
+    $userAccountControl |= 0x00000040; # ADS_UF_PASSWD_CANT_CHANGE
+                                       # The user cannot change the password.
+    $self->set_fields(userAccountControl => $userAccountControl);
+
+    $self->base->log(LA_NOTICE, 'Mot de passe changÃ© pour %s', $self->id);
+
+    1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad/objects.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad/objects.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ad/objects.pm	(revision 2119)
@@ -0,0 +1,57 @@
+package LATMOS::Accounts::Bases::Ad::objects;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Ldap::objects);
+use Net::LDAP;
+use Net::LDAP::Entry;
+use Net::LDAP::Control::Paged;
+use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
+use Net::LDAP::Util     qw( escape_filter_value );
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ad - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Ad;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Ad, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Attributes.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Attributes.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Attributes.pm	(revision 2119)
@@ -0,0 +1,585 @@
+package LATMOS::Accounts::Bases::Attributes;
+
+# $Id$
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Log;
+
+use Date::Parse;
+use DateTime;
+use DateTime::TimeZone;
+
+
+=head1 NAME
+
+    LATMOS::Accounts::Bases::Attributes - Object to handle attribute schema
+
+=head1 FUNCTIONS
+
+=head2 new($attributes, $base_or_object, $maybe_otype)
+
+Instanciate a new Attributes object.
+
+=cut
+
+sub new {
+    my ($class, $attributes, $base_or_object, $maybe_otype) = @_;
+    
+    my ($object, $base, $otype) =
+    $base_or_object->isa('LATMOS::Accounts::Bases::Objects')
+        ? ($base_or_object, $base_or_object->base, $base_or_object->type)
+        : (undef, $base_or_object, $maybe_otype);
+
+    if (ref $attributes) {
+        $attributes->{_base} = $base;
+        $attributes->{_object} = $object;
+        $attributes->{_name} = $attributes->{name};
+        $attributes->{_otype} = $otype;
+        return bless($attributes, $class);
+    } else {
+        my $attr_info = $base->get_attr_schema($otype, $attributes) or return;
+
+        $attr_info->{_base} = $base;
+        $attr_info->{_object} = $object;
+        $attr_info->{_name} = $attributes;
+        $attr_info->{_otype} = $otype;
+
+        return bless($attr_info, $class);
+    }
+}
+
+=head2 base
+
+Return base handle
+
+=cut
+
+sub base  { $_[0]->{_base}  }
+
+=head2 name
+
+Return attribute name
+
+=cut
+
+sub name  { $_[0]->{_name}  }
+
+=head2 otype
+
+Return object type for this attribute
+
+=cut
+
+
+sub otype { $_[0]->{_otype} }
+
+=head2 mandatory
+
+Return true if attribute is mandatory
+
+=cut
+
+sub mandatory { $_[0]->{mandatory} || 0 }
+
+=head2 object
+
+Return handle object if any
+
+=cut
+
+sub object { $_[0]->{_object} }
+
+=head2 reference
+
+A object type this attribute refer to
+
+=cut
+
+sub reference {
+    my ($self) = @_;
+    if ($self->{reference} &&
+        $self->base->is_supported_object($self->{reference})) {
+        return $self->{reference};
+    } else {
+        return;
+    }
+}
+
+=head2 iname
+
+Return internal name of attributes
+
+=cut
+
+sub iname { $_[0]->{iname} || $_[0]->name }
+
+=head2 label
+
+Return the label to display
+
+=cut
+
+sub label { $_[0]->{label} || $_[0]->{_name} }
+
+=head2 has_values_list
+
+Return true if the attribute have a fixed list of accepted values
+
+=cut
+
+sub has_values_list {
+    my ($self) = @_;
+    if ($self->base->obj_attr_allowed_values(
+        $self->otype,
+        $self->name) ||
+        $self->{can_values} ||
+        $self->reference) {
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+=head2 can_values
+
+Return possible value allowed by this attribute
+
+=cut
+
+sub can_values {
+    my ($self) = @_;
+    if (my @values = $self->base->obj_attr_allowed_values(
+            $self->otype,
+            $self->name)) {
+        return @values;
+    } elsif ($self->{can_values}) {
+        if (ref $self->{can_values} eq 'ARRAY') {
+            return @{$self->{can_values}};
+        } elsif (ref $self->{can_values} eq 'CODE') {
+            $self->{can_values}->($self, $self->object);
+        } else {
+            return;
+        }
+    } elsif (my $ref = $self->reference) {
+        return $self->base->list_objects($ref);
+    } else { return }
+}
+
+=head2 display ($value)
+
+Return the well formated value according attribute
+
+=cut
+
+sub display {
+    my ($self, $value) = @_;
+    if ($self->{display}) {
+        return $self->{display}->($self, $value);
+    } else {
+        return $value;
+    }
+}
+
+=head2 input ($value)
+
+Return well formated single value for insert into base
+
+=cut
+
+sub input {
+    my ($self, $value) = @_;
+    if ($self->{input}) {
+        if (!defined($value)) { return }
+        return $self->{input}->($value);
+    } elsif ($self->real_form_type =~ /^(DATE|DATETIME)$/) {
+        if (!$value) {
+            return;
+        } else {
+            $value =~ s:^(\d+)/(\d+)/(\d+):$2/$1/$3:;
+            my $epoch = str2time($value);
+            return $value if (!defined($epoch));
+            my $dt = DateTime->from_epoch(epoch => str2time($value));
+            $dt->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+            if ($self->real_form_type eq 'DATE') {
+                return $dt->ymd('-');
+            } else {
+                return $dt->ymd('-') . ' ' . $dt->hms(':');
+            }
+        }
+    } else {
+        return $value;
+    }
+}
+
+=head2 checkinputformat ($value)
+
+Check input value format, return false on error
+
+=cut
+
+sub checkinputformat {
+    my ($self, $value) = @_;
+
+    if ($self->{checkinputformat}) {
+        if (!$self->{checkinputformat}->($value)) {
+           return;
+        }
+    }
+
+    return 1;
+}
+
+=head2 checkinput ($value)
+
+Check input value, return false on error
+
+=cut
+
+sub checkinput {
+    my ($self, $values) = @_;
+
+    foreach my $value (grep { $_ } (ref $values ? @{ $values } : $values)) {
+        if (!$self->checkinputformat($value)) {
+            $self->base->log(LA_ERR, "Wrong format for  %s/%s: %s",
+               $self->name,
+               $self->{_otype},
+               $value
+            );
+            return;
+        }
+    }
+
+    if ($self->mandatory &&
+        (!(defined($values)) || $values eq '')) {
+        $self->base->log(LA_ERR,
+            "%s attribute cannot be empty for object %s",
+            $self->name,
+            $self->{_otype},
+        );
+        return;
+    }
+
+    if (ref $values eq 'ARRAY' && ! $self->multiple) {
+        $self->base->log(LA_WARN, 'Attribute %s is not multi valuesd', $self->name);
+        # TODO: really return an error
+        # return;
+    }
+
+    if (defined($values) && $self->{checkinput}) {
+        foreach my $val (ref $values ? @{ $values } : $values) {
+            if (!$self->{checkinput}->($val)) {
+                return;
+            }
+        }
+        return 1;
+    }
+
+    if ($self->has_values_list && $values) {
+        my @possible = $self->can_values;
+        foreach my $value (ref $values ? @{ $values } : $values) {
+            if (! grep { $value eq $_ } @possible) {
+                $self->base->log(LA_ERR,
+                    "%s attribute cannot have `%s' as value for object %s/%s",
+                    $self->name,
+                    $value,
+                    $self->object->type,
+                    $self->object->id,
+                ) if ($self->object);
+                $self->base->log(LA_DEBUG, "Possible value for %s: %s",
+                    $self->name,
+                    join(', ', @possible)
+                );
+                return;
+            }
+        }
+    }
+
+
+    return 1;
+}
+
+=head2 readable
+
+Return true if attribute can be read
+
+=cut
+
+sub readable {
+    my ($self) = @_;
+    if (ref $self->{readable} eq 'CODE') {
+        return $self->{readable}->($self->object) || 0;
+    } else {
+        return defined($_[0]->{readable}) ? $_[0]->{readable} : 1;
+    }
+}
+
+=head2 ro
+
+Return true if the attribute cannot be write by design
+
+=cut
+
+sub ro {
+    my ($self) = @_;
+    if (ref $self->{ro} eq 'CODE') {
+        return $self->{ro}->($self->object) || 0;
+    } else {
+        return $_[0]->{ro} || 0 
+    }
+}
+
+=head2 readonly
+
+Return true if attribute cannot be read according acls or attributes state
+
+=cut
+
+sub readonly { 
+    my ($self) = @_;
+    return 1 if ($self->ro);
+    
+    return ! $self->check_acl('w');
+}
+
+=head2 check_acl ($mode)
+
+Return true is access to C<$mode> is granted
+
+=cut
+
+sub check_acl {
+    my ($self, $mode) = @_;
+
+    return 1 if ($self->{_noacl});
+
+    return $self->base->check_acl($self->object
+        ? ($self->object, $self->name, $mode)
+        : ($self->otype, '@CREATE', $mode));
+}
+
+=head2 real_form_type
+
+Return the way the fields must be show in GUI.
+For each type option maybe given by form_option
+
+=head3 LABEL
+
+=over 4
+
+=item length
+
+The length to use to show the attribute
+
+=back
+
+=head3 TEXT
+
+=head3 TEXTAREA
+
+=head3 DATE
+
+=head3 LIST
+
+=head3 CHECKBOX
+
+=over 4
+
+=item rawvalue
+
+The real value of the attribute must be show
+
+=back
+
+=cut
+
+sub real_form_type { $_[0]->{formtype} || 'TEXT' }
+
+=head2 form_type
+
+Return the way the attribute must be displayed
+
+=cut
+
+sub form_type {
+    $_[0]->readonly ? 'LABEL' :
+    $_[0]->{formtype} ? $_[0]->{formtype} :
+    $_[0]->has_values_list ? 'LIST' :
+    $_[0]->real_form_type
+}
+
+=head2 form_option ($option)
+
+Return the form option C<$option>
+
+=cut
+
+sub form_option {
+    my ($self, $option) = @_;
+    return $self->{formopts}{$option}
+}
+
+=head2 uniq
+
+Return true is attribute value must be uniq
+
+=cut
+
+sub uniq { $_[0]->{uniq} || 0 }
+
+=head2 multiple
+
+Return true is attribute value can be set several times
+
+=cut
+
+sub multiple { $_[0]->{multiple} || 0 }
+
+=head2 hidden
+
+Return true if attributes must not appear in list by can be query.
+
+=cut
+
+sub hidden { $_[0]->{hide} || 0 }
+
+=head2 delayed
+
+Return true if attribute must be set after object creation during
+synchronisation
+
+=cut
+
+sub delayed { $_[0]->{delayed} || 0 }
+
+=head2 get
+
+Return the value for this attribute
+
+=cut
+
+sub get {
+    my ($self) = @_;
+
+    if (defined($self->object)) {
+        my $res = (ref($self->{get}) eq 'CODE')
+            ? $self->{get}->($self)
+            : $self->object->get_field($self->iname);
+
+        # always return undef if empty
+        # and an array if attribute is multivalued
+        if (!defined($res)) {
+            return undef;
+        } elsif (ref $res) {
+            return $res;
+        } elsif ($self->multiple) {
+            return (ref $res ? $res : [$res])
+        } else {
+            return $res;
+        }
+    } else {
+        return;
+    }
+}
+
+=head2 defaultValue
+
+Return the default static value for this attribute.
+
+=cut
+
+sub defaultValue {
+    my ($self) = @_;
+
+    return $self->base->defaultAttributeValue($self->otype, $self->name)
+}
+
+=head2 getValues
+
+Return value for this attribute, results are always return as an array
+
+=cut
+
+sub getValues {
+    my ($self) = @_;
+
+    my $res = $self->get();
+
+    return ref $res ? grep { $_ } @{ $res } : $res;
+}
+
+
+=head2 set ($values)
+
+Set attribute value to attached object
+
+=cut
+
+sub set {
+    my ($self, $values) = @_;
+
+    my $inputv = ref $values
+        ? [ map { $self->input($_) } @$values ]
+        : $values ? $self->input($values) : undef;
+
+    my $res = ref $self->{set} eq 'CODE'
+        ? $self->{set}->($self, $inputv)
+        : $self->object->set_fields($self->iname, $inputv);
+
+    # If the attribute has a post code, call it
+    if ($res && $self->{post}) {
+        $self->{post}->($self, $inputv);
+    }
+
+    $res
+}
+
+=head2 default_value
+
+Return default value for this attribute
+
+=cut
+
+sub default_value {
+    my ($self) = @_;
+    $self->defaultValue;
+}
+
+=head2 monitored
+
+Return true if the attribute is monitored
+
+=cut
+
+sub monitored {
+    my ($self) = @_;
+
+    if ($self->iname ne $self->name) {
+        my $attr = $self->base->attribute($self->otype, $self->iname) or return;
+        return $attr->monitored;
+    } else {
+        return $self->{monitored} || 0;
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Dummy.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Dummy.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Dummy.pm	(revision 2119)
@@ -0,0 +1,44 @@
+package LATMOS::Accounts::Bases::Dummy;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Bases);
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Dummy - Dummy base for testing purpose
+
+=head1 DESCRIPTION
+
+This base does nothing except returning true, it can be used to test process
+
+=cut
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+sub new {
+    my ($class, %config) = @_;
+    bless {}, $class;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Dummy/User.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Dummy/User.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Dummy/User.pm	(revision 2119)
@@ -0,0 +1,56 @@
+package LATMOS::Accounts::Bases::Dummy::User;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Bases::Objects);
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Dummy::User - User support for Dummy base
+
+=cut
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+sub _get_attr_schema {
+    {
+        username => {},
+        homeDirectory => { iname => 'home' },
+    }
+}
+
+sub list {
+    return ();
+}
+
+sub new {
+    my ($class, $base, $id) = @_;
+    bless { _id => $id }, $class;
+}
+
+sub _create {
+    my ($class, $id);
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>, L<LATMOS::Accounts::Bases::Sql::DataRequest>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Heimdal.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Heimdal.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Heimdal.pm	(revision 2119)
@@ -0,0 +1,209 @@
+package LATMOS::Accounts::Bases::Heimdal;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases);
+use LATMOS::Accounts::Log;
+use Heimdal::Kadm5;
+
+our $VERSION = (q$Rev: 653 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Heimdal - Latmos Accounts support for kerberos/heimdal
+
+=head1 SYNOPSIS
+
+  use LATMOS::Heimdal;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Heimdal, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 new(%config)
+
+Create a new LATMOS::Ldap object for windows AD $domain.
+
+config:
+
+=over 4
+
+=item domain
+
+The DNS domain where information about kerberos server can be found, eg the
+REALM in the B<TXT> record B<kerberos> and the admin server from B<SRV> record
+B<_kerberos-adm._tcp>.
+
+This paramater can be omited if both B<realm> and B<server> are set.
+
+=item realm
+
+The kerberos realm to connect to. Can be omit if B<domain> is given and this DNS
+domain contains kerberos record.
+
+See B<domain> parameter.
+
+=item server
+
+If set, try to connect to this server, if not set, a dns query is performed
+to find kerberos admin server, first responding is used.
+
+See B<domain> parameter.
+
+=item ignoredusers
+
+A coma separated list of user (login) to not modify or delete in the database.
+
+The user named 'default' is automatically ignored.
+
+=back
+
+=cut
+
+sub new {
+    my ($class, %config) = @_;
+    
+    bless({}, $class);
+}
+
+sub load {
+    my ($self) = @_;
+
+    # If already loaded, just say ok !
+    $self->{_heimdal} and return 1;
+
+    if (!$self->config('realm')) {
+        if ($self->config('domain')) {
+            $self->{_config}{realm} = $self->_domain2realm or do {
+                $self->log(LA_ERR,
+                    'Cannot find kerberos TXT record for domain `%s\'',
+                    $self->config('domain'),
+                );
+                return;
+            };
+            $self->log(LA_DEBUG, 'kerberos REALM is %s', $self->config('realm'));
+        } else {
+            # No way to find realm
+            return;
+        }
+    }
+    my @servers = $self->config('server')
+        ? ($self->config('server'))
+        : $self->config('domain')
+            ? $self->_domain2server
+            : ();
+
+    $self->log(LA_DEBUG, 'kerberos servers are %s', join(', ', @servers));
+
+    if (!@servers) {
+        $self->log(LA_ERROR, "no heimdal servers found");
+        return;
+    }
+
+    foreach my $server (@servers) {
+        my $heimdal = Heimdal::Kadm5::Client->new(
+            # RaiseErrors => 1,
+            Server => $server,
+            # Port   => '8899',
+            # Required:
+            Principal => $self->config('login'),
+            Realm  => $self->config('realm'),
+            # --- Either ---
+            Password => $self->config('password'),
+            # --- Or ---
+            # Keytab => $self->config('keytab'),
+        );
+
+        if($heimdal) {
+            $self->log(LA_DEBUG, "Connected to heidmal server %s",
+                $server);
+            $self->{_heimdal} = $heimdal;
+            last;
+        } else {
+            $self->log(LA_ERR, "Cannot connect to %s", $server);
+        }
+    }
+
+    if (!$self->{_heimdal}) {
+        $self->log(LA_ERR, "Cannot connect to any kerberos server");
+        return;
+    }
+
+    return 1;
+}
+
+sub _domain2server {
+    my ($self) = @_;
+    require Net::DNS;
+    my @servers;
+
+    my $resolver = Net::DNS::Resolver->new;
+    my $query = $resolver->query("_kerberos-adm._tcp." . $self->config('domain'),
+        "SRV") or return;
+    foreach my $rr (
+        sort { $a->priority <=> $b->priority || $a->weight <=> $b->weight }
+        grep { $_->type eq 'SRV' } $query->answer) {
+        push(@servers, $rr->target); # $rr->port)); don't use port
+    }
+
+    @servers
+}
+sub _domain2realm {
+    my ($self) = @_;
+    require Net::DNS;
+
+    my $resolver = Net::DNS::Resolver->new;
+    my $query = $resolver->query(
+        "_kerberos." . $self->config('domain'),
+        "TXT") or return;
+    foreach my $rr ($query->answer) {
+        $rr->type eq 'TXT' and return $rr->txtdata;
+    }
+
+    return;
+}
+
+=head2 heimdal
+
+Return heimdal handle.
+
+=cut
+
+sub heimdal {
+    return $_[0]->{_heimdal};
+}
+
+package Heimdal::Kadm5; 
+sub DESTROY { }
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Heimdal/User.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Heimdal/User.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Heimdal/User.pm	(revision 2119)
@@ -0,0 +1,226 @@
+package LATMOS::Accounts::Bases::Heimdal::User;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Objects);
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev: 652 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ldap - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Ldap;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Ldap, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _get_attr_schema {
+    my ($class, $base, $info) = @_;
+    $info ||= {};
+
+    foreach (qw(
+        uid
+        modName
+	    )) {
+        $info->{$_} = { ro => 1 };
+    }
+    $info->{userPassword} = { ro => 1, readable => 0 };
+    foreach (qw(
+        krb5ValidEnd
+        krb5KDCFlags
+        krb5MaxRenew
+        krb5MaxLife
+	    )) {
+        $info->{$_} = { };
+    }
+
+    return $info;
+}    
+
+sub _is_ignored_user {
+    my ($base, $uid) = @_;
+
+    $uid eq 'default' and return 1;
+    grep { $uid eq $_ } split(/ *, */, $base->config('ignoredusers'))
+        and return 1;
+
+    return;
+}
+
+sub list {
+    my ($class, $base) = @_;
+
+    my $realm = $base->config('realm');
+    my @uids;
+    foreach ($base->heimdal->getPrincipals('*@' . $realm)) {
+        /^([^@\/]+)\@\Q$realm\E$/ or next;
+        my $uid = $1;
+        _is_ignored_user($base, $uid) and next;
+        push(@uids, $uid);
+    }
+    
+    return @uids;
+
+}
+
+=head2 uid2principal ($uid, $base)
+
+Convert UID to kerberos principal
+
+=cut
+
+sub uid2principal {
+    my ($self, $uid, $base) = @_;
+    my $ba = $base || $self->base;
+    $uid ||= $self->id;
+    return $uid . '@' . $ba->config('realm');
+}
+
+
+sub new {
+    my ($class, $base, $uid) = @_;
+    
+    _is_ignored_user($base, $uid) and return;
+    my $entry = $base->heimdal->getPrincipal($class->uid2principal($uid, $base));
+    return if (!$entry);
+    bless({ entry => $entry, _base => $base, _id => $uid }, $class);
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+
+    _is_ignored_user($base, $id) and do {
+        $base->log('User %s is ignored by config, creation refused', $id);
+        return;
+    };
+    my $principal = $base->heimdal->makePrincipal($class->uid2principal($id, $base));
+    exists($data{krb5KDCFlags}) or $data{krb5KDCFlags} = 0;
+    exists($data{krb5MaxRenew}) or $data{krb5MaxRenew} = 604800;
+    exists($data{krb5MaxLife}) or $data{krb5MaxLife} = 86400;
+
+    _setprincipal($principal, %data);
+    my $pass = join('', map { ('a'..'z')[rand 26] } (0..10));
+    return $base->heimdal->createPrincipal($principal, $pass);
+
+    return 1;
+}
+
+sub _delete {
+    my ($class, $base, $uid) = @_;
+    _is_ignored_user($base, $uid) and return;
+    my $obj = $class->new($base, $uid) or return;
+
+    my $code = $base->heimdal->deletePrincipal($class->uid2principal($uid, $base));
+
+    if (!$code) {
+        $base->log(LA_ERR, "Cannot delete object %s", $uid);
+        return;
+    } else {
+        $base->log(LA_INFO, "Object (%s) %s delete", $class->type, $uid);
+        return 1
+    }
+}
+
+sub _rename {
+    my ($class, $base, $uid, $newuid) = @_;
+    my $obj = $class->new($base, $uid) or return;
+
+    my $mesg = $base->ldap->moddn( $obj->{entry},
+        newrdn => 'cn=' .  escape_filter_value($newuid),
+        deleteoldrdn => 1,
+    );
+
+    if ($mesg->code) {
+        $base->log(LA_ERR, "Cannot rename object %s: %s", $uid, $mesg->error);
+        return;
+    } else {
+        return 1;
+    }
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+
+    for ($field) {
+        /uid/ and return $self->id;
+        /modName/ and return $self->{entry}->getModName();
+        /krb5ValidEnd/ and do {
+            # (2^31) - 1 means no expiration
+            my $val = $self->{entry}->getPrincExpireTime();
+            return $val == 2_147_483_647 ? undef : $val;
+        };
+        /krb5KDCFlags/ and return $self->{entry}->getAttributes();
+        /krb5MaxRenew/ and return $self->{entry}->getMaxRenewableLife();
+        /krb5MaxLife/ and return $self->{entry}->getMaxLife();
+        /userPassword/ and return '';
+    }
+
+    return;
+}
+
+sub _setprincipal {
+    my ($principal, %fields) = @_;
+    for (keys %fields) {
+        # It's seems krb5ValidEnd cannot be unset using perl API
+        # so we're setting the expiration to max time (2^31 - 1)
+        /krb5ValidEnd/ and $principal->setPrincExpireTime($fields{$_} || 2_147_483_647);
+        /krb5KDCFlags/ and $principal->setAttributes($fields{$_} || 0);
+        /krb5MaxRenew/ and $principal->setMaxRenewableLife($fields{$_} || 0);
+        /krb5MaxLife/ and $principal->setMaxLife($fields{$_} || 0);
+    }
+}
+
+
+sub set_fields {
+    my ($self, %fields) = @_;
+
+    _setprincipal($self->{entry}, %fields);
+
+    return $self->base->heimdal->modifyPrincipal($self->{entry});
+}
+
+sub _set_password {
+    my ($self, $clear_pass) = @_;
+
+    $self->base->heimdal->changePassword(
+        $self->uid2principal(),
+        $clear_pass
+    );
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap.pm	(revision 2119)
@@ -0,0 +1,292 @@
+package LATMOS::Accounts::Bases::Ldap;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases);
+use Net::LDAP;
+use Net::LDAP::Entry;
+use Net::LDAP::Control::Paged;
+use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
+use Net::LDAP::Util     qw( escape_filter_value );
+use Unicode::Map8;
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev: 653 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ldap - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Ldap;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Ldap, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 new(%config)
+
+Create a new LATMOS::Ldap object for windows AD $domain.
+
+config:
+
+=over 4
+
+=item domain
+
+The Active directory domain
+
+=item server (optional)
+
+If set, try to connect to this server, if not set, a dns query is performed
+to find AD server, first responding is used.
+
+=item ssl
+
+If set, try to connect using ssl
+
+=item OBJECT_container
+
+The sub path where to find object type of OBJECT and where they should
+be created
+
+=back
+
+=cut
+
+sub new {
+    my ($class, %config) = @_;
+    
+    my $self = {
+        _top_dn => $config{topdn},
+    };
+
+    bless($self, $class);
+}
+
+=head2 object_base_dn ($otype)
+
+Return the dn of obecjt containers
+
+=cut
+
+sub object_base_dn {
+    my ($self, $otype) = @_;
+    return join(',',
+        ($self->config($otype . '_container') || 'cn=Users'),
+        $self->top_dn,
+    );
+}
+
+=head2 sambaSID($id)
+
+Return the base samba SID set in the config or a default one if none is set.
+
+If C<$id> is given return the full SID suitable for an object.
+
+=cut
+
+sub sambaSID {
+    my ($self, $id) = @_;
+
+    my $ssid = $self->config('sambaSID') || 'S-2016-01-07';
+    if (defined($id)) {
+        $ssid .= '-' . $id;
+    }
+    return $ssid;
+}
+
+sub load {
+    my ($self) = @_;
+
+    # If already loaded, just say ok !
+    $self->{_ldap} and return 1;
+
+    my $ldapurl = $self->_ldap_url($self->config('server'));
+    my $ldap = Net::LDAP->new($ldapurl, async => 0);
+        
+
+    if($ldap) {
+        $self->log(LA_DEBUG, "Connected to ldap server %s", $ldapurl);
+    } else {
+        $self->log(LA_ERR, "Cannot connect to %s", $ldapurl);
+        return; # cannot connect to any ldap :\
+    };
+
+    if ($self->config('tls')) {
+        $self->log(LA_DEBUG, "Running start_tls()");
+        $ldap->start_tls(
+            verify => 'none',
+        );
+    }
+
+    my $msg = $ldap->bind($self->config('login'), password => $self->config('password'));
+    $msg->code and do {
+        $self->log(LA_ERR, "Cannot bind ldap: %s", $msg->error);
+        return;
+    };
+
+    $self->{_ldap} = $ldap;
+    return 1;
+}
+
+sub _ldap_url {
+    my ($self, $host, $port) = @_;
+
+    sprintf(
+        '%s://%s%s/',
+        $self->config('ssl') ? 'ldaps' : 'ldap',
+        $host,
+        $port ? ":$port" : '',
+    )
+}
+
+=head2 ldap
+
+Return the L<Net::LDAP> handle
+
+=cut
+
+sub ldap {
+    return $_[0]->{_ldap};
+}
+
+=head2 top_dn
+
+Return the TOP DN of the AD zone.
+
+=cut
+
+sub top_dn {
+    return $_[0]->{_top_dn}
+}
+
+# _unlimited_search
+
+# By default, ldap servers limit results to avoid deni of services.
+# LDAP protocol provide a paging feature to fetch all results.
+
+#This function works like Net::LDAP::search functions, but return nothing,
+#use "callback" option to get entries.
+
+sub _unlimited_search {
+    my $self = shift;
+    my @args = @_;
+
+    my ($page, $cookie) = (Net::LDAP::Control::Paged->new( size => 100 ));
+
+    while (1) {
+        my $search = $self->ldap->search(
+            @args,
+            control => [ $page ],
+        );
+        if ($search->code) {
+            return $search;
+        }
+
+        ### After foreach loops ends, client checks LDAP server reponse of how many search results total.
+        ### This is a control to end the infinite while loop if there are no search results to go through 
+        ### If there are search results, this control will always return the total number of results. It is
+        ### never decremented
+        my ($resp) = $search->control( LDAP_CONTROL_PAGED ) or last;
+
+
+        ### Obtaining the cookie from the search result. When no more results, cookie will be NULL
+        ### and infinite while loop will terminate.
+        $cookie = $resp->cookie or last;
+
+        ### Sets cookie so server knows the next search result to send 
+        $page->cookie($cookie);
+    }
+    ### This is a control to check for abnormal exit of the while loop.
+    ### If this occurs ### we need to tell the LDAP server that remaining
+    ### search results are no longer needed ### by sending a search request with a page size of 0
+    if ($cookie)    {
+        $page->cookie($cookie);
+        $page->size(0);
+        $self->ldap->search(@args, control => [ $page ]);
+    }
+    return 1;
+}
+
+# _get_object_from_dn($dn)
+#
+# Return Net::LDAP::Entry for $dn
+
+sub _get_object_from_dn {
+    my ($self, $dn) = @_;
+
+    my $mesg = $self->ldap->search(
+        base => $dn,
+        scope => 'base',
+        filter => '(objectClass=*)',
+    );
+    if ($mesg->code) {
+        $self->log(LA_ERR, "Cannot get object %s: %s", $dn, $mesg->error);
+        return;
+    }
+
+    return ($mesg->entries)[0];
+}
+
+sub authenticate_user {
+    my ($self, $username, $passwd) = @_;
+    $username or return;
+
+    # Basically, connect and if bind succeed, user is authenticated !
+    my $ldapurl = $self->ldap->uri;
+    my $ldap = Net::LDAP->new($ldapurl);
+
+    my $obj = $self->get_object('user', $username) or do {
+        $self->log(LA_ERR, "Cannot find user %s", $username);
+        return;
+    };
+
+    if ($ldap) {
+        $self->log(LA_DEBUG, "Connected to ldap server %s", $ldapurl);
+    } else {
+        $self->log(LA_ERR, "Cannot connect to %s", $ldapurl);
+        return; # cannot connect to any ldap :\
+    };
+
+
+    my $msg = $ldap->bind($obj->get_attributes('dn'), password => $passwd);
+    if ($msg->code) {
+        $self->log(LA_ERR, "Cannot bind ldap: %s", $msg->error);
+        return;
+    } else {
+        return 1; # Success !! \o/
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/Aliases.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/Aliases.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/Aliases.pm	(revision 2119)
@@ -0,0 +1,116 @@
+package LATMOS::Accounts::Bases::Ldap::Aliases;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Ldap::objects);
+use Net::LDAP;
+use Net::LDAPS;
+use Net::LDAP::Entry;
+use Net::LDAP::Control::Paged;
+use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
+use Net::LDAP::Util     qw( escape_filter_value );
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev: 649 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ldap - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Ldap;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Ldap, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _class_filter { '(&(ObjectClass=nisMailAlias))' }
+
+sub _key_attr { 'cn' } 
+
+sub _my_ldap_classes { qw(
+    top
+    nisMailAlias
+) }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+    my $info = {
+        cn => { ro => 1 },
+        dn => { ro => 1 },
+        objectClass => { ro => 1 },
+        rfc822MailMember => {
+            multiple => 1,
+        },
+    };
+
+    $info->{$class->_key_attribute($base)}{ro} = 1;
+    $info->{$class->_dn_attribute($base)}{ro} = 1;
+
+    $info
+}
+
+sub is_supported {
+    my ($class, $base) = @_;
+    return $base->config('aliases_container') ? 1 : 0;
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+
+    my $entry = Net::LDAP::Entry->new();
+
+    $entry->dn(join(',',
+        sprintf('%s=%s',
+            $class->_dn_attribute($base),
+            escape_filter_value($id)),
+            $base->object_base_dn($class->type),
+    ));
+    $entry->replace(objectClass =>
+        [ $class->_my_ldap_classes ],);
+    $data{$class->_key_attribute($base)} = $id;
+    foreach (keys %data) {
+        $class->_populate_entry($entry,
+            $_, $data{$_}, $base);
+    }
+
+    my $msg = $base->ldap->add($entry);
+    if ($msg->code) {
+        $base->log(LA_ERR, "Cannot create aliases: %s", $msg->error);
+        return 0;
+    }
+
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2015 CNRS LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/Group.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/Group.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/Group.pm	(revision 2119)
@@ -0,0 +1,141 @@
+package LATMOS::Accounts::Bases::Ldap::Group;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Ldap::objects);
+use Net::LDAP;
+use Net::LDAP::Entry;
+use Net::LDAP::Control::Paged;
+use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
+use Net::LDAP::Util     qw( escape_filter_value );
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev: 649 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ldap - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Ldap;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Ldap, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _class_filter { '(ObjectClass=posixGroup)' }
+
+sub _key_attr { 'cn' } 
+
+sub _my_ldap_classes { qw(top posixGroup) }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+    $class->SUPER::_get_attr_schema($base,
+    {
+        gidNumber => { uniq => 1, },
+        description => { },
+        memberUID => { multiple => 1, delayed => 1, },
+        cn => { ro => 1 },
+        dn => { ro => 1 },
+        objectClass => { ro => 1 },
+    }
+    );
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+
+    my $entry = Net::LDAP::Entry->new();
+
+    $entry->dn(join(',',
+        sprintf('%s=%s',
+            $class->_dn_attribute($base),
+            escape_filter_value($id)),
+            $base->object_base_dn($class->type),
+    ));
+    $entry->replace(objectClass => [ $class->_my_ldap_classes ],);
+    my %delayed;
+    $data{$class->_key_attribute($base)} = $id;
+    foreach (keys %data) {
+        /^(memberUID)$/ and do {
+            $delayed{memberUID} = $data{$_};
+            next;
+        };
+        $class->_populate_entry($entry, $_, $data{$_});
+    }
+    my $msg = $base->ldap->add($entry);
+    $base->log(LA_ERR, "Cannot create group: %s", $msg->error) if ($msg->code);
+    return if ($msg->code);
+    if (! keys %delayed) { return 1 };
+    my $res = $base->get_object('group', $id)->set_fields(%delayed);
+    return defined($res) ? 1 : 0;
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+
+    $field eq 'memberUID' and do {
+        my $val = $self->SUPER::get_field('memberUid');
+        return ref $val ? $val : [ grep { $_ } $val ];
+    };
+    $self->SUPER::get_field($field);
+}
+
+sub set_fields {
+    my ($self, %data) = @_;
+    my %ndata;
+    while (my ($f, $val) = each(%data)) {
+        $f eq 'memberUID' and do {
+            $val = [ $val ] unless(ref $val);
+            my %users;
+            $users{$_}{e} = 1 foreach (@{ $self->get_field('memberUID') || []});
+            $users{$_}{n} = 1 foreach (grep { $_ } @{ $val || []});
+            foreach (keys %users) {
+                $users{$_}{e} && $users{$_}{n} and next;
+                if ($users{$_}{e}) {
+                    $self->{entry}->delete(memberUid => $_);
+                } elsif ($users{$_}{n} && $self->base->get_object('user', $_)) {
+                    $self->{entry}->add(memberUid => $_);
+                } # else {} # can't happen
+                my $mesg = $self->{entry}->update($self->base->ldap);
+            }
+            next;
+        };
+        $ndata{$f} = $val;
+    }
+    $self->SUPER::set_fields(%ndata);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/Onlyaddress.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/Onlyaddress.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/Onlyaddress.pm	(revision 2119)
@@ -0,0 +1,151 @@
+package LATMOS::Accounts::Bases::Ldap::Onlyaddress;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Ldap::objects);
+use Net::LDAP;
+use Net::LDAPS;
+use Net::LDAP::Entry;
+use Net::LDAP::Control::Paged;
+use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
+use Net::LDAP::Util     qw( escape_filter_value );
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev: 649 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ldap - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Ldap;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Ldap, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _class_filter { '(&(ObjectClass=inetOrgPerson))' }
+
+sub _key_attr { 'cn' } 
+
+sub _my_ldap_classes { qw(
+    top
+    inetOrgPerson
+    organizationalPerson
+) }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+    my $info = {
+        # inetOrgPerson
+        displayName => {},
+        givenName => {},
+        initials => {},
+        # mail => {},
+        sn => {},
+        mobile => {},
+        o => {},
+        uid => {},
+        facsimileTelephoneNumber => {},
+        # organizationalPerson
+        street => {},
+        postOfficeBox => {},
+        postalCode => {},
+        postalAddress => {},
+        streetAddress => {},
+        physicalDeliveryOfficeName => {},
+        ou => {},
+        st => {},
+        l => {},
+        telephoneNumber => {},
+        cn => { ro => 1 },
+        dn => { ro => 1 },
+        objectClass => { ro => 1 },
+    };
+
+    $info->{$class->_key_attribute($base)}{ro} = 1;
+    $info->{$class->_dn_attribute($base)}{ro} = 1;
+
+    $info
+}
+
+sub is_supported {
+    my ($class, $base) = @_;
+    return $base->config('onlyaddress_container') ? 1 : 0;
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+
+    my $entry = Net::LDAP::Entry->new();
+
+    $entry->dn(join(',',
+        sprintf('%s=%s',
+            $class->_dn_attribute($base),
+            escape_filter_value($id)),
+            $base->object_base_dn($class->type),
+    ));
+    $entry->replace(objectClass =>
+        [ $class->_my_ldap_classes ],);
+    $data{sn} ||= $id; # sn is mandatory
+    $data{$class->_key_attribute($base)} = $id;
+    foreach (keys %data) {
+        $class->_populate_entry($entry,
+            $_, $data{$_}, $base);
+    }
+
+    my $msg = $base->ldap->add($entry);
+    if ($msg->code) {
+    	$base->log(LA_ERR, "Cannot create onlyaddress: %s", $msg->error);
+    	return 0;
+    }
+    return 1;
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+
+    $field eq 'streetAddress' and $field = 'street';
+    $self->SUPER::get_field($field);
+}
+
+sub _populate_entry {
+    my ($self, $entry, $f, $val, $base) = @_;
+    $base ||= $self->base;
+    for ($f) {
+        /^sn$/ and $val ||= $entry->get_value($self->_key_attribute($base));
+    }
+    $self->SUPER::_populate_entry($entry, $f, $val, $base);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/User.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/User.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/User.pm	(revision 2119)
@@ -0,0 +1,340 @@
+package LATMOS::Accounts::Bases::Ldap::User;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Ldap::objects);
+use Net::LDAP;
+use Net::LDAPS;
+use Net::LDAP::Entry;
+use Net::LDAP::Control::Paged;
+use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
+use Net::LDAP::Util     qw( escape_filter_value );
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Bases::Ldap::Group;
+use LATMOS::Accounts::Utils;
+use Crypt::SmbHash;
+
+our $VERSION = (q$Rev: 649 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ldap - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Ldap;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Ldap, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _class_filter { '(&(ObjectClass=posixAccount))' }
+
+sub _key_attr { 'cn' } 
+
+sub _my_ldap_classes { qw(
+    top
+    inetOrgPerson
+    organizationalPerson
+    posixAccount
+    shadowAccount
+    sambaSamAccount
+) }
+
+sub _computeSSID {
+    my ($value) = @_;
+    $value * 2 + 1000
+}
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+    $class->SUPER::_get_attr_schema($base,
+    {
+        displayName => { },
+        givenName => { },
+        homePhone => { },
+        homePostalAddress => { },
+        initials => { },
+        mail => { },
+        sn => { },
+        mobile => { },
+        o => { },
+        uid => { mandatory => 1 },
+        manager => {
+            delayed => 1,
+            can_values => sub { $base->list_objects('user') },
+        },
+        facsimileTelephoneNumber => { },
+        uidNumber => {
+            uniq => 1,
+            post => sub {
+                my ($self, $value) = @_;
+                $self->object->set_fields(sambaSID => $self->object->base->sambaSID(_computeSSID($value)));
+            },
+        },
+        gidNumber => {
+            reference => 'group',
+            mandatory => 1,
+            can_values => sub {
+                map { $base->get_object('group',
+                        $_)->get_attributes('gidNumber') }
+                $base->list_objects('group')
+            },
+            display => sub {
+                my ($self, $val) = @_;
+                my ($gr) = $self->base->search_objects('group', "gidNumber=$val")
+                    or return;
+                return $gr;
+            },
+
+        },
+        homeDirectory => { },
+        loginShell => { },
+        gecos => { },
+        description => { },
+        shadowLastChange => { },
+        shadowMin => { },
+        shadowMax => { },
+        shadowWarning => { },
+        shadowInactive => { },
+        shadowExpire => { },
+        shadowFlag => { },
+        street => { },
+        postOfficeBox => { },
+        postalCode => { },
+        postalAddress => { },
+        streetAddress => { },
+        physicalDeliveryOfficeName => { },
+        ou => { },
+        st => { },
+        l => { },
+        telephoneNumber => { },
+        memberOf => {
+            delayed => 1,
+            multiple => 1,
+            reference => 'group',
+        },
+        cn => { ro => 1, },
+        dn => { ro => 1, },
+        objectClass => { ro => 1, },
+        pwdAccountLockedTime => {},
+        pwdPolicySubentry => {},
+        pwdChangedTime => { ro => 1 },
+        labeledURI => {},
+        userPassword    => { readable => 0, },
+        sambaLMPassword => { readable => 0, },
+        sambaNTPassword => { readable => 0, },
+        sambaSID => { ro => 1 },
+    }
+    );
+}
+
+sub _update_class {
+    my ($self) = @_;
+
+    $self->SUPER::_update_class(
+        sambaSID => $self->base->sambaSID(
+            _computeSSID($self->_get_attributes('uidNumber'))
+        )
+    );
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+
+    my $entry = Net::LDAP::Entry->new();
+
+    $entry->dn(join(',',
+        sprintf('%s=%s',
+            $class->_dn_attribute($base),
+            escape_filter_value($id)),
+            $base->object_base_dn($class->type),
+        )
+    );
+    $entry->replace(objectClass =>
+        [ $class->_my_ldap_classes ],);
+    $data{cn} ||= $data{sn} || $id; # cn is mandatory
+    $data{sn} ||= $id; # sn is mandatory
+    $data{uid} ||= $id; # uid is mandatory
+    $data{sambaSID} = $base->sambaSID(_computeSSID($data{uidNumber}));
+    $data{homeDirectory} ||= '/dev/null'; # homeDirectory is mandatory
+    $data{$class->_key_attribute($base)} = $id;
+    foreach (keys %data) {
+        $class->_populate_entry($entry,
+            $_, $data{$_}, $base);
+    }
+
+    my $msg = $base->ldap->add($entry);
+    if ($msg->code) {
+    	$base->log(LA_ERR, "Cannot create user: %s", $msg->error);
+    	return 0;
+    }
+    return 1;
+}
+
+sub _rename {
+    my ($class, $base, $uid, $newuid) = @_;
+
+    $class->SUPER::_rename($base, $uid, $newuid) or return;
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+
+    $field eq 'streetAddress' and $field = 'street';
+    $field eq 'memberOf' and do {
+        my @res;
+        $self->base->_unlimited_search(
+            base => $self->base->object_base_dn('group'),
+            filter => sprintf(
+                '(&(objectClass=posixGroup)(memberUID=%s))',
+                escape_filter_value($self->id),
+            ),
+            callback => sub {
+                my ($mesg, $entry) = @_;
+                ref $entry eq 'Net::LDAP::Entry' or return;
+                push(@res, $entry->get_value(
+                    LATMOS::Accounts::Bases::Ldap::Group->_key_attribute($self->base),
+                ));
+                1;
+            },
+        );
+        return [ sort(@res) ];
+    };
+    $field eq 'manager' and do {
+        my $dn = $self->SUPER::get_field($field) or return;
+        my $manager = $self->base->_get_object_from_dn($dn) or return;
+        return $manager->get_value(__PACKAGE__->_key_attribute($self->base));
+    };
+    $self->SUPER::get_field($field);
+}
+
+sub _populate_entry {
+    my ($self, $entry, $f, $val, $base) = @_;
+    $base ||= $self->base;
+    for ($f) {
+        /^sn$/ and $val ||= $entry->get_value($self->_key_attribute);
+        /^memberOf$/ and do {
+            my %users;
+            $users{$_}{e} = 1 foreach (ref $self
+                ? $self->get_attributes('memberOf')
+                : ());
+            $val = [ $val ] unless(ref $val);
+            $users{$_}{n} = 1 foreach (grep { $_ } @{ $val || []});
+            foreach (keys %users) {
+                $users{$_}{e} && $users{$_}{n} and next;
+                my $group = $base->get_object('group', $_) or next;
+                if ($users{$_}{e}) {
+                    $group->{entry}->delete(memberUID =>
+                        $entry->get_value(
+                            LATMOS::Accounts::Bases::Ldap::User->_key_attribute($base),
+                        )
+                    );
+                } elsif ($users{$_}{n}) {
+                    $group->{entry}->add(memberUID =>
+                        $entry->get_value(
+                            LATMOS::Accounts::Bases::Ldap::User->_key_attribute($base),
+                        )
+                    );
+                } # else {} # can't happen
+                my $mesg = $group->{entry}->update($base->ldap);
+                if ($mesg->code) {
+                    $base->log(LA_ERR, "Cannot set attributes: %s", $mesg->error);
+                    return;
+                }
+            }
+            return 1;
+        };
+        /^manager$/ && $val and do {
+            my $user = $base->get_object('user', $val) or do {
+                $val = undef;
+                next;
+            };
+            $val = $user->get_field('dn');
+            next;
+        };
+        /^gecos$/ and do {
+            $val = to_ascii($val);
+        };
+        /^homeDirectory$/ and $val ||= '/dev/null';
+    }
+    $self->SUPER::_populate_entry($entry, $f, $val, $base);
+}
+
+sub _set_password {
+    my ($self, $clear_pass) = @_;
+
+    # openldap use prefix to identify encryption passwd
+    # {CRYPT} is system dependant, eg use crypt from system
+    # As we run openldap on UNIX, this should not be a problem
+    # as we use perl crypt() which does the same
+    # This code will have to be changed if we use openldap on other UNIX
+    my $crypt = '{CRYPT}' . $self->base->passCrypt($clear_pass);
+
+    my ($lm, $nt) = ntlmgen $clear_pass;
+
+    my $res = $self->set_fields(
+        userPassword    => $crypt,
+        sambaLMPassword => $lm,
+        sambaNTPassword => $nt,
+    );
+    $self->base->log(LA_NOTICE, 'Mot de passe changÃ© pour %s', $self->id)
+    if($res);
+    return $res;
+}
+
+=head2 _InjectCryptPasswd($cryptpasswd)
+
+Inject a password encrypted using standard UNIX method.
+
+Works only for unix authentification method inside LDAP
+
+=cut
+
+sub _InjectCryptPasswd {
+    my ($self, $cryptpasswd) = @_;
+
+    my $res = $self->set_fields(
+        userPassword => '{CRYPT}' . $cryptpasswd,
+    );
+
+    if ($res) {
+        $self->base->log(LA_NOTICE, 'Crypted password injected for %s', $self->id);
+        return 1;
+    } else {
+        $self->base->log(LA_ERR, 'Cannot inject crypted password for %s', $self->id);
+        return 0;
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/objects.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/objects.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Ldap/objects.pm	(revision 2119)
@@ -0,0 +1,301 @@
+package LATMOS::Accounts::Bases::Ldap::objects;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Objects);
+use Net::LDAP;
+use Net::LDAP::Entry;
+use Net::LDAP::Control::Paged;
+use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
+use Net::LDAP::Util     qw( escape_filter_value );
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev: 652 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ldap - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Ldap;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Ldap, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _get_attr_schema {
+    my ($class, $base, $info) = @_;
+    $info ||= {};
+
+    foreach (qw(
+	    createTimestamp
+	    creatorsName
+	    entryUUID
+	    modifiersName
+	    modifyTimestamp
+	    entryCSN
+	    )) {
+        $info->{$_} = { ro => 1 };
+    }
+
+    $info->{$class->_key_attribute($base)}{ro} = 1;
+    $info->{$class->_dn_attribute($base)}{ro} = 1;
+
+    return $info;
+}    
+
+sub _key_attribute {
+    my ($self, $base) = @_;
+    $base ||= $self->base;
+
+    $base->config($self->type . '_key_attribute') || $self->_key_attr || 'cn';
+}
+
+sub _dn_attribute {
+    my ($self, $base) = @_;
+    $base ||= $self->base;
+
+    $base->config($self->type . '_dn_attribute') || $self->_key_attr || 'cn';
+}
+
+sub list {
+    my ($class, $base) = @_;
+    
+    my @uids;
+    eval {
+    my $xx = $base->_unlimited_search(
+        attrs => [ $class->_key_attribute($base) ],
+        base => $base->object_base_dn($class->type),
+        filter => $class->_class_filter,
+        callback => sub {
+            my ($mesg, $entry) = @_;
+            #$mesg->code and die $mesg->error;
+            $entry or return;
+            ref $entry eq 'Net::LDAP::Entry' or return;
+            push(@uids, $entry->get_value( $class->_key_attribute($base) ));
+        },
+    );
+    };
+
+    return @uids;
+
+}
+
+sub new {
+    my ($class, $base, $uid) = @_;
+    
+    my $mesg = $base->ldap->search(
+        filter => sprintf(
+            '(&%s (%s=%s))',
+            $class->_class_filter,
+            $class->_key_attribute($base),
+            escape_filter_value($uid),
+        ),
+        base => $base->object_base_dn($class->type),
+        attrs => [ $class->_canonical_fields($base, 'r') ],
+    );
+
+    if ($mesg->code)  {
+        $base->log(LA_ERR, 'Cannot fetch %s/%s: %s', $class->type, $uid, $mesg->code);
+        return;
+    }
+
+    my ($entry, @others) = $mesg->entries;
+
+    if(@others) { # we cannot have multiple entries...
+        $base->la_log(LA_ERR, 'Multiple entry found forr %s/%s', $class->type, $uid);
+        return;
+    }
+    if (!$entry) {
+        $base->log(LA_DEBUG, 'Cannot fetch %s/%s: no entry returned', $class->type, $uid);
+        return;
+    }
+    bless({ entry => $entry, _base => $base, _id => $uid }, $class);
+}
+
+sub _delete {
+    my ($class, $base, $uid) = @_;
+    my $obj = $class->new($base, $uid) or return;
+
+    my $mesg = $base->ldap->delete($obj->{entry}->dn);
+
+    if ($mesg->code) {
+        $base->log(LA_ERR, "Cannot delete object %s: %s", $uid, $mesg->error);
+        return;
+    } else {
+        $base->log(LA_INFO, "Object (%s) %s delete", $class->type, $uid);
+        return 1
+    }
+}
+
+sub _rename {
+    my ($class, $base, $uid, $newuid) = @_;
+    my $obj = $class->new($base, $uid) or return;
+
+    my $mesg;
+
+    if ($class->_key_attribute($base) ne
+                $class->_dn_attribute($base)) {
+
+        my $entry = $obj->{entry}->clone;
+        $entry->add(
+            $class->_key_attribute($base),
+            $newuid
+        );
+        $mesg = $entry->update($base->ldap);
+        if ($mesg->code && $mesg->code ne 20) {
+            $base->log(LA_ERR, "Cannot update key_attribute for object %s: %s", $uid, $mesg->error);
+            return;
+        }
+    }
+
+
+    $mesg = $base->ldap->moddn(
+        $obj->{entry},
+        newrdn => $class->_dn_attribute($base) . '=' .  escape_filter_value($newuid),
+        deleteoldrdn => 1,
+    );
+    if ($mesg->code) {
+        $base->log(LA_ERR, "Cannot rename object %s: %s", $uid, $mesg->error);
+        return;
+    }
+
+    $mesg = $base->ldap->search(
+        filter => sprintf(
+            '(&%s (%s=%s))',
+            $class->_class_filter,
+            $class->_key_attribute($base),
+            escape_filter_value($newuid),
+        ),
+        base => $base->object_base_dn($class->type),
+        attrs => [ $class->_canonical_fields($base, 'r') ],
+    );
+
+    if ($mesg->code)  {
+        $base->log(LA_ERR, 'Cannot fetch %s/%s: %s', $class->type, $uid, $mesg->code);
+        return;
+    }
+
+    my ($entry, @others) = $mesg->entries;
+
+    $obj->{entry} = $entry;
+
+    $obj->{entry}->replace(
+        $class->_key_attribute($base),
+        $newuid
+    );
+    $mesg = $obj->{entry}->update($base->ldap);
+
+    if ($mesg->code) {
+        $base->log(LA_ERR, "Cannot rename object %s: %s", $uid, $mesg->error);
+        return;
+    } else {
+        return 1;
+    }
+}
+
+=head2 ldap
+
+A shortcut to return the L<Net::LDAP> object
+
+=cut
+
+sub ldap {
+    return $_[0]->base->{_ldap};
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+
+    $field eq 'dn' and return $self->{entry}->dn;
+    my ($first, @others) = $self->{entry}->get_value($field);
+    return @others ? [ sort($first, @others) ] : $first;
+}
+
+sub _populate_entry {
+    my ($self, $entry, $field, $value, $base) = @_;
+    my $val = ref $self ? $self->get_field($field) : undef;
+    my $tr =  join(', ', map { $_ || '' } ($field, $val, $value));
+    if ($value) {
+        if ((!$val) || $val ne $value) {
+    	    $entry->replace($field, $value);
+        }
+    } elsif($val) {
+        $entry->delete($field);
+    }
+}
+
+sub set_fields {
+    my ($self, %fields) = @_;
+
+    {
+        my $oclass = join(',', sort $self->{entry}->get_value('objectClass'));
+        my $cclass = join(',', sort $self->_my_ldap_classes);
+        if ($oclass ne $cclass) {
+            $self->_update_class();
+        }
+    }
+    foreach (keys %fields) {
+        my $attr = $self->attribute($_) or do {
+            $self->base->log(LA_ERR, "Unknow attribute %s (%s)",
+                $_, $self->type);
+            return;
+        };
+        $attr->ro and next;
+        $self->_populate_entry($self->{entry}, $_, $fields{$_});
+    }
+    
+    my $mesg = $self->{entry}->update($self->base->ldap);
+
+    if ($mesg->code && $mesg->code != 82) {
+        $self->base->log(LA_ERR, "Cannot set attributes: %s", $mesg->error);
+        return;
+    } else { return 1 }
+}
+
+sub _update_class {
+    my ($self, %attr) = @_;
+
+    $self->base->log(
+        LA_NOTICE,
+        "Updating ObjectClass for %s/%s: %s",
+        $self->type, $self->id, join(', ', $self->_my_ldap_classes)
+    );
+    $self->{entry}->replace(
+        'objectClass' => [ $self->_my_ldap_classes ],
+        %attr,
+    );
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Mail.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Mail.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Mail.pm	(revision 2119)
@@ -0,0 +1,192 @@
+package LATMOS::Accounts::Bases::Mail;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases);
+use LATMOS::Accounts::Log;
+use Fcntl qw(:flock);
+
+our $VERSION = (q$Rev: 641 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Mail - Base support for mail files
+
+=head1 DESCRIPTION
+
+This base is designed to generated aliases/revaliases files used by postfix or
+sendmail. This allow to centralise all information and to automated alias
+generation from user real name.
+
+=cut
+
+sub new {
+    my ($class, %config) = @_;
+
+    my $base = {
+        file => {},
+        aliases => {},
+        revaliases => {},
+    };
+
+    foreach (qw(aliases revaliases)) {
+        if ($config{$_}) {
+            $base->{file}{$_} = $config{$_};
+        } elsif ($config{directory}) {
+            $base->{file}{$_} = $config{directory} . '/' . $_;
+        } else {
+            $base->{file}{$_} = "/etc/$_";
+        }
+    }
+
+
+    bless($base, $class);
+}
+
+sub load {
+    my ($self) = @_;
+
+    # If aleady loaded just say ok !
+    $self->{_loaded} and return 1;
+
+    # warning, comment will be strip !
+    if (open(my $handle, '<', $self->{file}{aliases})) {
+        $self->log(LA_DEBUG, "Reading file %s", $self->{file}{aliases});
+        flock($handle, LOCK_EX);
+        while (my $line = <$handle>) {
+            chomp($line);
+            $line =~ s/^\s*#.*//;
+            $line or next;
+            my ($name, $forward) = $line =~ /^([^:]+):?\s*(.*)$/;
+            $name or do {
+                next;
+            };
+            $forward or next;
+            $self->{aliases}{$name} = $forward;
+        }
+        close($handle);
+    } else {
+        $self->log(LA_ERR, "Cannot open file '%s` for reading",
+            $self->{file}{aliases});
+        return;
+    }
+
+    if (open(my $handle, '<', $self->{file}{revaliases})) { 
+        $self->log(LA_DEBUG, "Reading file %s", $self->{file}{revaliases});
+        flock($handle, LOCK_EX);
+        while (my $line = <$handle>) {
+            chomp($line);
+            $line =~ s/^\s*#.*//;
+            my ($name, $as) = $line =~ /^(\S+)(?::\s*|\s+)(\S+)$/;
+            $name or next;
+            $as or do { warn $line; next};
+            $self->{revaliases}{$name} = $as;
+        }
+        close($handle);
+    } else {
+        $self->log(LA_ERR, "Cannot open file '%s` for reading",
+            $self->{file}{revaliases});
+        return;
+    }
+
+    $self->{_loaded} = 1;
+
+    return 1;
+}
+
+sub _commit {
+    my ($self) = @_;
+
+    if (open(my $handle, '>>', $self->{file}{aliases})) {
+        flock($handle, LOCK_EX);
+        truncate($handle, 0);
+        foreach (sort keys %{ $self->{aliases} }) {
+            printf $handle "%s:%s\n", $_, $self->{aliases}{$_};
+        }
+        close($handle);
+        $self->log(LA_INFO, $self->{file}{aliases} . " saved");
+    } else {
+        $self->log(LA_ERR, "Cannot open file '%s` for writing",
+            $self->{file}{aliases});
+        return;
+    }
+
+    if (open(my $handle, '>', $self->{file}{revaliases})) {
+        flock($handle, LOCK_EX);
+        truncate($handle, 0);
+        foreach (sort keys %{ $self->{revaliases} }) {
+            printf $handle "%s %s\n", $_, $self->{revaliases}{$_};
+        }
+        close($handle);
+        $self->log(LA_INFO, $self->{file}{revaliases} . " saved");
+    } else {
+        $self->log(LA_ERR, "Cannot open file '%s` for writing",
+            $self->{file}{revaliases});
+        return;
+    }
+
+}
+
+sub list_objects {
+    my ($self, $type) = @_;
+
+    $type = lc($type);
+    lc($type) =~ /^(aliases|revaliases)$/ or return;
+
+    return sort keys %{ $self->{$type} };
+}
+
+sub create_object {
+    my ($self, $otype, $id, %data) = @_;
+
+    $otype = lc($otype);
+    if ($self->{$otype}{$id}) {
+        return;
+    } elsif ($otype =~ /^aliases$/i) {
+        $data{forward} ||= [];
+        $self->{$otype}{$id} = ref $data{forward}
+            ? join(',', @{$data{forward}})
+            : $data{forward};
+        return 1;
+    } elsif ($data{as} && $otype =~ /^revaliases$/i) {
+        $self->{$otype}{$id} = $data{as};
+        return 1;
+    } else {
+        return;
+    }
+}
+
+sub _delete_object {
+    my ($self, $otype, $id) = @_;
+    
+    $otype = lc($otype);
+    $otype =~ /^(aliases|revaliases)$/ or return;
+    if ($self->{$otype}{$id}) {
+        delete($self->{$otype}{$id});
+        return 1;
+    } else {
+        return;
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Mail/Aliases.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Mail/Aliases.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Mail/Aliases.pm	(revision 2119)
@@ -0,0 +1,76 @@
+package LATMOS::Accounts::Bases::Mail::Aliases;
+
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Mail::objects);
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev: 641 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Mail::Aliases - Aliases support for mail base.
+
+=cut
+
+sub new {
+    my ($class, $base, $id, @args) = @_;
+    if (exists($base->{aliases}{$id})) {
+        return bless({}, $class);
+    } else { return }
+}
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+    {
+        forward => {
+            multiple => 1,
+        },
+    }
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+    $field eq 'forward' and do {
+        my @forw = sort split(',', $self->base->{aliases}{$self->id});
+        return scalar(@forw) > 1
+            ? [ @forw ]
+            : $self->base->{aliases}{$self->id};
+    };
+    return
+}
+
+sub set_fields {
+    my ($self, %data) = @_;
+    if ($data{forward}) {
+        $self->base->{aliases}{$self->id} = ref $data{forward}
+            ? join(',', sort @{$data{forward}})
+            : $data{forward};
+        return 1;
+    }
+    return;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Mail>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Mail/Revaliases.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Mail/Revaliases.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Mail/Revaliases.pm	(revision 2119)
@@ -0,0 +1,67 @@
+package LATMOS::Accounts::Bases::Mail::Revaliases;
+
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Mail::objects);
+use LATMOS::Accounts::Log;
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Mail::Revaliases - RevAliases support for Mail base.
+
+=cut
+
+our $VERSION = (q$Rev: 641 $ =~ /^Rev: (\d+) /)[0];
+
+sub new {
+    my ($class, $base, $id, @args) = @_;
+    if (exists($base->{revaliases}{$id})) {
+        return bless({}, $class);
+    } else { return }
+}
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+    {
+        as => {},
+    }
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+    $field eq 'as' and return $self->base->{revaliases}{$self->id};
+    return
+}
+
+sub set_fields {
+    my ($self, %data) = @_;
+    if ($data{as}) {
+        $self->base->{revaliases}{$self->id} = $data{as};
+        return 1;
+    }
+    return
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Mail>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Mail/objects.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Mail/objects.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Mail/objects.pm	(revision 2119)
@@ -0,0 +1,31 @@
+package LATMOS::Accounts::Bases::Mail::objects;
+
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Objects);
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev: 641 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Mail::objects - Parent class for Mail base's objects
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Mail>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/OCHelper.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/OCHelper.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/OCHelper.pm	(revision 2119)
@@ -0,0 +1,158 @@
+package LATMOS::Accounts::Bases::OCHelper;
+
+# $Id: OCHelper.pm 2932 2010-08-10 17:19:21Z nanardon $
+
+use strict;
+use warnings;
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::OCHelper - Object creation helper
+
+=head1 DESCRIPTION
+
+This module is designed to be subclassed.
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 new($base, $otype)
+
+=cut
+
+sub new {
+    my ($class, $base, $otype) = @_;
+    bless { 
+        _base => $base,
+        _otype => $otype,
+    }, $class;
+}
+
+=head2 $ochelper->base
+
+Return base object
+
+=cut
+
+sub base { $_[0]->{_base} }
+
+=head2 $ochelper->otype
+
+Return object type for this OChelper.
+
+=cut
+
+sub otype { $_[0]->{_otype} }
+
+=head2 $ochelper->step($info)
+
+Process next step by submitting C<$info> and return the status and new
+information to continue process.
+
+C<$info> must look like:
+
+    info = {
+      step => 0,
+      name => { # name of object
+        ask => 0/1,
+        content => ...
+      },
+      ask => [ list ],
+      contents => { name => ... }
+    }
+
+STATUS will be one of 'NEEDINFO', 'CREATED', 'ERROR', undef.
+
+=cut
+
+sub step {
+    my ($self, $info) = @_;
+    $info ||= {};
+    $info->{step} ||= 0;
+    $info->{ask} = [];
+    $info->{name}{ask} = 0;
+    my $otype = $self->otype;
+    foreach (keys %{ $self->base->{defattr} || {} }) {
+        /^$otype\.(.*)/ or next;
+        my $attr = $1;
+        my $oattr = $self->base->attribute($otype, $attr) or next;
+        $oattr->ro and next;
+        $info->{contents}{$attr} = $self->base->{defattr}{$_}
+            unless exists($info->{contents}{$attr});
+    }
+
+    return($self->_step($info), $info);
+
+}
+
+# just return status, $info is reference
+sub _step {
+    my ($self, $info) = @_;
+
+    if ($info->{step} == 0) {
+        $info->{name}{ask} = 1;
+        foreach ($self->base->list_canonical_fields($self->otype, 'w')) {
+            /^oalias$/ and next;
+            push(@{$info->{ask}}, $_);
+        }
+        $info->{step} = 1;
+        return 'NEEDINFO';
+    } elsif ($info->{step} == 1) {
+        if ($self->base->create_c_object($self->otype,
+                $info->{name}{content},
+                %{$info->{contents} || {}},
+            )) {
+            return 'CREATED';
+        } else {
+            return 'ERROR';
+        }
+    } else {
+        return undef;
+    }
+}
+
+=head2 $ochelper->Automate($info)
+
+Try to create object from C<$info> w/o interacting with user.
+If given infomation does not allow to create object, it failed.
+
+Return 1 on success.
+
+=cut
+
+sub Automate {
+    my ($self, $info) = @_;
+
+    for (my $count = 0; $count < 3; $count++) {
+        my $status;
+        ($status, $info) = $self->step($info);
+        if ($status eq 'CREATED') {
+            return 1;
+        } elsif ($status eq 'ERROR') {
+            return;
+        }
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases>, L<LATMOS::Accounts::Bases::Objects>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/OCHelper/Group.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/OCHelper/Group.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/OCHelper/Group.pm	(revision 2119)
@@ -0,0 +1,58 @@
+package LATMOS::Accounts::Bases::OCHelper::Group;
+
+# $Id: User.pm 2932 2010-08-10 17:19:21Z nanardon $
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Bases::OCHelper);
+use LATMOS::Accounts::Utils;
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::OCHelper::User -
+Helper to create group in L<LATMOS::Accounts> system
+
+=cut
+
+sub _step {
+    my ($self, $info) = @_;
+    if ($info->{step} <= 0) {
+        $info->{name}{ask} = 1;
+        push(@{$info->{ask}}, qw(description label gidNumber managedBy sutype ));
+        $info->{step} = 1;
+    } 
+    elsif ($info->{step} == 1) {
+        if ($self->base->create_c_object($self->otype,
+                $info->{name}{content},
+                %{$info->{contents} || {}},
+            )) {
+            return 'CREATED';
+        } else {
+            return 'ERROR';
+        }
+    } else {
+        return undef;
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::OCHelper>
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/OCHelper/Nethost.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/OCHelper/Nethost.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/OCHelper/Nethost.pm	(revision 2119)
@@ -0,0 +1,73 @@
+package LATMOS::Accounts::Bases::OCHelper::Nethost;
+
+# $Id: OCHelper.pm 2932 2010-08-10 17:19:21Z nanardon $
+
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::OCHelper);
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::OCHelper - Object creation helper
+
+=head1 DESCRIPTION
+
+This module is designed to be subclassed.
+
+=head1 FUNCTIONS
+
+=cut
+
+# just return status, $info is reference
+sub _step {
+    my ($self, $info) = @_;
+
+    if ($info->{step} == 0) {
+        $info->{name}{ask} = 1;
+        @{$info->{ask}} = qw(
+            description
+            ip
+            ipFrom
+            macaddr
+            owner
+            user
+            unexported
+        );
+        $info->{step} = 1;
+        return 'NEEDINFO';
+    } elsif ($info->{step} == 1) {
+        if ($self->base->create_c_object($self->otype,
+                $info->{name}{content},
+                %{$info->{contents} || {}},
+            )) {
+            return 'CREATED';
+        } else {
+            return 'ERROR';
+        }
+    } else {
+        return undef;
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases>, L<LATMOS::Accounts::Bases::Objects>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/OCHelper/User.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/OCHelper/User.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/OCHelper/User.pm	(revision 2119)
@@ -0,0 +1,113 @@
+package LATMOS::Accounts::Bases::OCHelper::User;
+
+# $Id: User.pm 2932 2010-08-10 17:19:21Z nanardon $
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Bases::OCHelper);
+use LATMOS::Accounts::Utils;
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::OCHelper::User -
+Helper to create user in L<LATMOS::Accounts> system
+
+=cut
+
+sub _step {
+    my ($self, $info) = @_;
+    if ($info->{step} <= 0) {
+        $info->{name}{ask} = 1;
+        push(@{$info->{ask}}, qw(givenName sn));
+        $info->{step} = 1;
+        if (!($info->{name}{content} || 
+              ($info->{contents}{sn}) && $info->{contents}{givenName})) {
+            return 'NEEDINFO';
+        }
+    } 
+    if ($info->{step} == 1) {
+        $info->{step} = 2;
+        my $login = $info->{name}{content};
+        if (!$login) {
+            $info->{name}{ask} = 1;
+            # Try to find a login:
+            
+            if ($info->{contents}{sn}) {
+                my $login = buildLogin(
+                    sub {
+                        !$self->base->get_object('user', $_[0]);
+                    },
+                    $info->{contents}{sn},
+                    $info->{contents}{givenName},
+                );
+                if ($login) {
+                    $info->{name}{content} = $login;
+                }
+            }
+        } else {
+            $self->base->get_object('user', $login) and do {
+                $info->{name}{ask} = 1;
+            }
+        }
+        push(@{$info->{ask}}, 'uidNumber');
+        push(@{$info->{ask}}, 'gidNumber');
+
+       my %default = $self->base->compute_default(
+            'user', $login || '',
+            %{ $info->{contents} || {} }
+        );
+
+        if ($default{mail}) {
+            push(@{$info->{ask}}, 'mail');
+            $info->{contents}{mail} = $default{mail};
+        }
+        if (my $attr = $self->base->attribute('user', 'aliases')) {
+            if (!$attr->ro) {
+                push(@{$info->{ask}}, 'aliases');
+                $info->{contents}{aliases} = $default{aliases};
+            }
+        }
+        if (my $attr = $self->base->attribute('user', 'revaliases')) {
+            if (!$attr->ro) {
+                push(@{$info->{ask}}, 'revaliases');
+                $info->{contents}{revaliases} = $default{revaliases};
+            }
+        }
+        return 'NEEDINFO';
+
+
+    } elsif ($info->{step} == 2) {
+        if ($self->base->create_c_object($self->otype,
+                $info->{name}{content},
+                %{$info->{contents} || {}},
+            )) {
+            return 'CREATED';
+        } else {
+            return 'ERROR';
+        }
+    } else {
+        return undef;
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::OCHelper>
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Objects.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Objects.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Objects.pm	(revision 2119)
@@ -0,0 +1,870 @@
+package LATMOS::Accounts::Bases::Objects;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use overload '""' => 'stringify';
+
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Bases::Attributes;
+use Crypt::Cracklib;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Objects - Base class for account objects
+
+=head1 SYNOPSIS
+
+  use LATMOS::Accounts::Bases::Objects;
+  LATMOS::Accounts::Bases::Objects->new($base, $type, $id);
+
+=head1 DESCRIPTION
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 is_supported
+
+If exists, must return true or false if the object is supported or not
+
+=cut
+
+=head2 list($base)
+
+List object supported by this module existing in base $base
+
+Must be provide by object class
+
+    sub list {
+        my ($class, $base) = @_;
+    }
+
+=cut
+
+=head2 listReal($base)
+
+List object supported by this module existing in base $base
+
+Can be override by base driver. The result must exclude specials object such alias.
+
+=cut
+
+sub listReal {
+    my ($class, $base) = @_;
+    $class->list($base);
+}
+
+=head2 list_from_rev($base, $rev)
+
+List objects create or modified after base revision C<$rev>.
+
+=cut
+
+=head2 new($base, $id)
+
+Create a new object having $id as uid.
+
+=cut
+
+sub new {
+    my ($class, $base, $id, @args) = @_;
+    # So can be call as $class->SUPER::new()
+    bless {
+        _base => $base,
+        _type => lc(($class =~ m/::([^:]*)$/)[0]),
+        _id => $id,
+    }, $class;
+}
+
+=head2 _create($class, $base, $id, %data)
+
+Must create a new object in database.
+
+Is called if underling base does not override create_object
+
+    sub _create(
+        my ($class, $base, $id, %data)
+    }
+
+=cut
+
+=head2 type
+
+Return the type of the object
+
+=cut
+
+sub type {
+    my ($self) = @_;
+    if (ref $self) {
+        return $self->{_type}
+    } else {
+        return lc(($self =~ /::([^:]+)$/)[0]);
+    }
+}
+
+=head2 base
+
+Return the base handle for this object.
+
+=cut
+
+sub base {
+    return $_[0]->{_base}
+}
+
+=head2 id
+
+Must return the unique identifier for this object
+
+=cut
+
+sub id {
+    my ($self) = @_;
+    $self->{_id}
+}
+
+=head2 Iid
+
+Return internal id if different from Id
+
+=cut
+
+sub Iid {
+    my ($self) = @_;
+    $self->id
+}
+
+=head2 stringify
+
+Display object as a string
+
+=cut
+
+sub stringify {
+    my ($self) = @_;
+
+    return $self->id
+}
+
+=head2 list_canonical_fields($for)
+
+Object shortcut to get the list of field supported by the object.
+
+=cut
+
+sub list_canonical_fields {
+    my ($self, $for) = @_;
+    $for ||= 'rw';
+    $self->_canonical_fields($for);
+}
+
+=head2 attribute ($attribute)
+
+Return L<LATMOS::Accounts::Bases::Attributes> object for C<$attribute>
+
+=cut
+
+sub attribute {
+    my ($self, $attribute) = @_;
+
+    my $attrinfo;
+    if (! ref $attribute) {
+        $attrinfo = $self->_get_attr_schema(
+            $self->base)->{$attribute}
+        or return;
+        $attrinfo->{name} = $attribute;
+    } else {
+        $attrinfo = $attribute;
+    }
+
+    return LATMOS::Accounts::Bases::Attributes->new(
+        $attrinfo,
+        $self,
+    );
+}   
+
+sub _canonical_fields {
+    my ($class, $base, $for) = @_;
+    $for ||= 'rw';
+    my $info = $base->_get_attr_schema($class->type);
+    my @attrs = map { $base->attribute($class->type, $_) } keys %{$info || {}};
+    @attrs = grep { ! $_->ro } @attrs if($for =~ /w/);
+    @attrs = grep { $_->readable } @attrs if($for =~ /r/);
+    map { $_->name } grep { !$_->hidden }  @attrs;
+}
+
+=head2 GetOtypeDef
+
+This function is called to provide sub object definition. Must be overwritten
+per object class when need.
+
+Must return a hashref listing each sub object type and their related key atribute:
+
+    return {
+        addresses => 'user',
+    }
+
+=cut
+
+sub GetOtypeDef {
+    my ($class) = @_;
+
+    return;
+}
+
+=head2 get_field($field)
+
+Return the value for $field, must be provide by data base.
+
+    sub get_field {
+        my ($self, $field)
+    }
+
+=cut
+
+=head2 get_c_field($cfield)
+
+Return the value for canonical field $cfield.
+
+Call driver specific get_field()
+
+=cut
+
+sub get_c_field {
+    my ($self, $cfield) = @_;
+    $self->base->check_acl($self, $cfield, 'r') or do {
+        $self->base->log(LA_ERR, "Permission denied to get %s/%s",
+            $self->id, $cfield
+        );
+        return;
+    };
+    return $self->_get_c_field($cfield);
+}
+
+=head2 get_attributes($attr)
+
+Like get_c_field but always return an array
+
+=cut
+
+sub get_attributes {
+    my ($self, $cfield) = @_;
+    my $res = $self->get_c_field($cfield);
+    if ($res) {
+        return(ref $res ? @{$res} : $res);
+    } else {
+        return;
+    }
+}
+
+sub _get_attributes {
+    my ($self, $cfield) = @_;
+    my $res = $self->_get_c_field($cfield);
+    if ($res) {
+        return(ref $res ? @{$res} : ($res));
+    } else {
+        return;
+    }
+}
+
+sub _get_c_field {
+    my ($self, $cfield) = @_;
+    my $attribute = $self->attribute($cfield) or do {
+        $self->base->log(LA_WARN, "Unknow attribute $cfield");
+        return;
+    };
+    $attribute->readable or do {
+        $self->base->log(LA_WARN, "Attribute $cfield is not readable");
+        return;
+    };
+    return $attribute->get; 
+}
+
+=head2 GetAttributeValue($cfield)
+
+Return the value to exposed to other base
+
+=cut
+
+sub GetAttributeValue {
+    my ($self, $cfield) = @_;
+
+    return $self->get_c_field($cfield);
+}
+
+
+=head2 queryformat ($fmt)
+
+Return formated string according C<$fmt>
+
+=cut
+
+sub queryformat {
+    my ($self, $fmt) = @_;
+    $fmt =~ s/\\n/\n/g;
+    $fmt =~ s/\\t/\t/g;
+    $fmt =~ s!
+        (?:%\{([^:}]*)(?::([^}]+))?\})
+        !
+        my $val = $self->get_c_field($1);
+        sprintf('%' . ($2 || 's'), ref $val ? join(',', @$val) : (defined($val) ? $val : ''))
+        !egx;
+    $fmt;
+}
+
+=head2 set_fields(%data)
+
+Set values for this object. %data is a list or peer field => values.
+
+    sub set_fields {
+        my ($self, %data) = @_;
+    }
+
+=cut
+
+=head2 checkValues ($base, $obj, %attributes)
+
+Allow to pre-check values when object are modified or created
+
+C<$obj> is either the new id at object creation or the object itself on modification.
+
+=cut
+
+sub checkValues {
+    my ($class, $base, $obj, %attributes) = @_;
+
+    return 1;
+}
+
+=head2 check_allowed_values ($attr, $values)
+
+Check if value C<$values> is allowed for attributes C<$attr>
+
+=cut
+
+sub check_allowed_values {
+    my ($self, $attr, $values) = @_;
+    $self->base->check_allowed_values($self->type, $attr, $values);
+}
+
+=head2 attr_allow_values ($attr)
+
+Return allowed for attribute C<$attr>
+
+=cut
+
+sub attr_allow_values {
+    my ($self, $attr) = @_;
+    return $self->base->obj_attr_allowed_values(
+        $self->type,
+        $attr,
+    );
+}
+
+=head2 set_c_fields(%data)
+
+Set values for this object. %data is a list or peer 
+canonical field => values. Fields names are translated.
+
+=cut
+
+sub set_c_fields {
+    my ($self, %cdata) = @_;
+    foreach my $cfield (keys %cdata) {
+        $self->base->check_acl($self, $cfield, 'w') or do { 
+            $self->base->log(LA_ERR, "Cannot modified %s/%s: %s",
+                $self->type, $self->id, "permission denied");
+            return;
+        };
+    }
+
+    foreach my $cfield (keys %cdata) {
+        $self->check_allowed_values($cfield, $cdata{$cfield}) or do {
+            $self->base->log(LA_ERR, "Cannot modified %s/%s: %s",
+                $self->type, $self->id, "non authorized value");
+            return;
+        };
+    }
+
+    $self->_set_c_fields(%cdata);
+}
+
+sub _set_c_fields {
+    my ($self, %cdata) = @_;
+    my %data;
+    my $res = 0;
+    foreach my $cfield (keys %cdata) {
+        my $attribute = $self->attribute($cfield) or do {
+            $self->base->log(LA_ERR,
+                "Cannot set unsupported attribute %s to %s (%s)",
+                $cfield, $self->id, $self->type
+            );
+            return;
+        };
+        $attribute->ro and do {
+            $self->base->log(LA_ERR,
+                "Cannot set read-only attribute %s to %s (%s)",
+                $cfield, $self->id, $self->type
+            );
+            return;
+        };
+
+        if (!$attribute->checkinput($cdata{$cfield})) {
+            $self->base->log(LA_ERR,
+                "Value for attribute %s to %s (%s) does not match requirements",
+                $cfield, $self->id, $self->type
+            );
+            return;
+        };
+    }
+
+    if (!$self->checkValues($self->base, $self, %cdata)) {
+        my $last = LATMOS::Accounts::Log::lastmessage(LA_ERR);
+        $self->base->log(LA_ERR,
+            "Cannot update %s (%s): wrong value%s",
+            $self->id, $self->type,
+            ($last ? ": $last" : $last)
+        );
+        return;
+    }
+
+    my %updated = ();
+    foreach my $cfield (keys %cdata) {
+        my $attribute = $self->attribute($cfield) or do {
+            $self->base->log(LA_ERR,
+                "Cannot set unsupported attribute %s to %s (%s)",
+                $cfield, $self->id, $self->type
+            );
+            return;
+        };
+        if ($attribute->set($cdata{$cfield})) {
+            $updated{$cfield} = $attribute->monitored;
+        }
+    }
+    
+    if (keys %updated) {
+        $self->ReportChange('Update', 'Attributes %s have been updated', join(', ', sort keys %updated));
+        foreach (sort keys %updated) {
+            $self->ReportChange('Attributes', '%s set to %s', $_, 
+                (ref $cdata{$_}
+                    ? join(', ', sort @{ $cdata{$_} })
+                    : $cdata{$_}) || '(none)')
+                if ($updated{$_});
+        }
+    }
+    return scalar(keys %updated);
+}
+
+=head2 addAttributeValue($attribute, $value)
+
+Add a value to a multivalue attributes
+
+=cut
+
+sub _addAttributeValue {
+    my ($self, $attribute, @values) = @_;
+
+    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
+    $self->_set_c_fields($attribute => [ @oldvalues, @values ]);
+}
+
+sub addAttributeValue {
+    my ($self, $attribute, @values) = @_;
+
+    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
+    $self->set_c_fields($attribute => [ @oldvalues, @values ]);
+}
+
+=head2 delAttributeValue($attribute, $value)
+
+Remove a value to a multivalue attributes
+
+=cut
+
+sub _delAttributeValue {
+    my ($self, $attribute, @values) = @_;
+
+    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
+
+    foreach my $value (@values) {
+        @oldvalues = grep { $_ ne $value } @oldvalues;
+    }
+
+    $self->_set_c_fields($attribute => @oldvalues ? [ @oldvalues, ] : undef );
+}
+
+sub delAttributeValue {
+    my ($self, $attribute, @values) = @_;
+
+    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
+
+    foreach my $value (@values) {
+        @oldvalues = grep { $_ ne $value } @oldvalues;
+    }
+
+    $self->set_c_fields($attribute => @oldvalues ? [ @oldvalues, ] : undef );
+}
+
+=head2 set_password($password)
+
+Set the password into the database, $password is the clear version
+of the password.
+
+This function store it into userPassword canonical field if supported
+using crypt unix and md5 algorythm (crypt md5), the salt is 8 random
+caracters.
+
+The base driver should override it if another encryption is need.
+
+=cut
+
+sub set_password {
+    my ($self, $clear_pass) = @_;
+    if ($self->base->check_acl($self, 'userPassword', 'w')) {
+        if ($self->_set_password($clear_pass)) {
+             $self->ReportChange('Password', 'user password has changed');
+             return 1;
+        } else {
+            return;
+        }
+    } else {
+        $self->base->log(LA_ERROR, "Permission denied for %s to change its password",
+            $self->id);
+        return;
+    }
+}
+
+sub _set_password {
+    my ($self, $clear_pass) = @_;
+    if (my $attribute = $self->base->attribute($self->type, 'userPassword')) {
+        my $res = $self->set_fields($attribute->iname, $self->base->passCrypt($clear_pass));
+        $self->base->log(LA_NOTICE, 'Mot de passe changÃ© pour %s', $self->id)
+            if($res);
+        return $res;
+    } else {
+        $self->base->log(LA_WARN,
+            "Cannot set password: userPassword attributes is unsupported");
+    }
+}
+
+=head2 check_password ($password)
+
+Check given password is secure using L<Crypt::Cracklib>
+
+=cut
+
+sub check_password {
+    my ( $self, $password ) = @_;
+    my $dictionary = $self->base->config('cracklib_dictionnary');
+
+    if ($password !~ /^[[:ascii:]]*$/) {
+       return "the password must contains ascii characters only";
+    }
+
+    return fascist_check($password, $dictionary);
+}
+
+=head2 InjectCryptPasswd($cryptpasswd)
+
+Inject a password encrypted using standard UNIX method.
+
+=cut
+
+sub InjectCryptPasswd {
+    my ($self, $cryptpasswd) = @_;
+
+    if ($self->can('_InjectCryptPasswd')) {
+        return $self->_InjectCryptPasswd($cryptpasswd);
+    } else {
+        $self->base->log(LA_ERR, 'Injecting unix crypt password is not supported');
+        return;
+    }
+}
+
+=head2 search ($base, @filter)
+
+Search object matching C<@filter>
+
+=cut
+
+sub search {
+    my ($class, $base, @filter) = @_;
+    my @results;
+    my %parsed_filter;
+    while (my $item = shift(@filter)) {
+        # attr=foo => no extra white space !
+        # \W is false, it is possible to have two char
+        my ($attr, $mode, $val) = $item =~ /^(\w+)(?:(\W)(.+))?$/ or next;
+        if (!$mode) {
+            $mode = '~';
+            $val = shift(@filter);
+        }
+        push(
+            @{$parsed_filter{$attr}},
+            {
+                attr => $attr,
+                mode => $mode,
+                val  => $val,
+            }
+        );
+    }
+    foreach my $id ($base->list_objects($class->type)) {
+        my $obj = $base->get_object($class->type, $id);
+        my $match = 1;
+        foreach my $field (keys %parsed_filter) {
+            $base->attribute($class->type, $field) or
+                la_log(LA_WARN, "Unsupported attribute %s", $field);
+            my $tmatch = 0;
+            foreach (@{$parsed_filter{$field}}) {
+                my $value = $_->{val};
+                my $fval = $obj->_get_c_field($field) || '';
+                if ($value eq '*') {
+                    if ($fval ne '') {
+                        $tmatch = 1;
+                        last;
+                    }
+                } elsif ($value eq '!') {
+                    if ($fval eq '') {
+                        $match = 1;
+                        last;
+                    }
+                } elsif ($_->{mode} eq '=') {
+                    if ($fval eq $value) {
+                        $tmatch = 1;
+                        last;
+                    }
+                } elsif($_->{mode} eq '~') {
+                    if ($fval =~ m/\Q$value\E/i) {
+                        $tmatch = 1;
+                        last;
+                    }
+                }
+            }
+            $match = 0 unless($tmatch);
+        }
+        push(@results, $id) if($match);
+    }
+    @results;
+}
+
+=head2 attributes_summary ($base, $attribute)
+
+Return list of values existing in base for C<$attribute>
+
+=cut
+
+sub attributes_summary {
+    my ($class, $base, $attribute) = @_;
+    my $attr = $base->attribute($class->type, $attribute) or do {
+        $base->log(LA_ERR, "Cannot instantiate %s attribute", $attribute);
+        return;
+    };
+    if (!$attr->readable) {
+        $base->log(LA_WARN, l('Attribute %s is not readable', $attribute));
+        return;
+    }
+    if (!$base->check_acl($class->type, $attribute, 'r')) {
+        $base->log(LA_WARN, l('Permission denied to read attribute %s', $attribute));
+        return;
+    }
+    my %values;
+    foreach my $id ($base->list_objects($class->type)) {
+        my $obj = $base->get_object($class->type, $id);
+        my $value = $obj->_get_c_field($attribute);
+        if ($value) {
+            if (ref $value) {
+                foreach (@$value) {
+                    $values{$_} = 1;
+                }
+            } else {
+                $values{$value} = 1;
+            }
+        }
+    }
+    return sort(keys %values);
+}
+
+=head2 attributes_summary_by_object ($base, $attribute)
+
+Return list of peer object <=> values
+
+=cut
+
+sub attributes_summary_by_object {
+    my ($class, $base, $attribute) = @_;
+    my $attr = $base->attribute($class->type, $attribute) or do {
+        $base->log(LA_ERR, "Cannot instantiate %s attribute", $attribute);
+        return;
+    };
+    if (!$attr->readable) {
+        $base->log(LA_WARN, l('Attribute %s is not readable', $attribute));
+        return;
+    }
+    if (!$base->check_acl($class->type, $attribute, 'r')) {
+        $base->log(LA_WARN, l('Permission denied to read attribute %s', $attribute));
+        return;
+    }
+    my %values;
+    foreach my $id ($base->list_objects($class->type)) {
+        my $obj = $base->get_object($class->type, $id);
+        my $value = $obj->_get_c_field($attribute);
+        if ($value) {
+            if (ref $value) {
+                foreach (@$value) {
+                    push(@{ $values{ $id } }, $_);
+                }
+            } else {
+                push(@{ $values{ $id } }, $value);
+            }
+        }
+    }
+    return %values;
+}
+
+=head2 find_next_numeric_id ($base, $field, $min, $max)
+
+Find next free uniq id for attribute C<$field>
+
+=cut
+
+sub find_next_numeric_id {
+    my ($class, $base, $field, $min, $max) = @_;
+    $base->attribute($class->type, $field) or return;
+    $min ||= 
+        $field eq 'uidNumber' ? 500 :
+        $field eq 'gidNumber' ? 500 :
+        1;
+    $max ||= 65635;
+    $base->log(LA_DEBUG, "Trying to find %s in range %d - %d",
+        $field, $min, $max);
+    my %existsid;
+    $base->temp_switch_unexported(sub {
+        foreach ($base->list_objects($class->type)) {
+            my $obj = $base->get_object($class->type, $_) or next;
+            my $id = $obj->_get_c_field($field) or next;
+            $existsid{$id + 0} = 1;
+        }
+    }, 1);
+    $min += 0;
+    $max += 0;
+    for(my $i = $min; $i <= $max; $i++) {
+        $existsid{$i + 0} or do {
+            $base->log(LA_DEBUG, "Next %s found: %d", $field, $i);
+            return $i;
+        };
+    }
+    return;
+}
+
+=head2 text_dump ($handle, $config, $base)
+
+Dump object into C<$handle>
+
+=cut
+
+sub text_dump {
+    my ($self, $handle, $config, $base) = @_;
+    print $handle $self->dump($config, $base);
+    return 1;
+}
+
+=head2 dump
+
+Return dump for tihs object
+
+=cut
+
+sub dump {
+    my ($self, $config, $base) = @_;
+
+    my $otype = $self->type;
+    $base ||= $self->base;
+    my $dump;
+    if (ref $self) {
+        $dump .= sprintf "# base %s: object %s/%s\n",
+            $base->label, $self->type, $self->id;
+    }
+    $dump .= sprintf "# %s\n", scalar(localtime);
+
+    foreach my $attr (sort { $a cmp $b } $base->list_canonical_fields($otype,
+        $config->{only_rw} ? 'rw' : 'r')) {
+        my $oattr = ref $self ? $self->attribute($attr) : $base->attribute($otype, $attr);
+        if ($oattr->hidden) { next; }
+        if (ref $self) {
+            my $val = $self->get_c_field($attr);
+            if ($val || $config->{empty_attr}) {
+                if (my @allowed = $base->obj_attr_allowed_values($otype, $attr)) {
+                    $dump .= sprintf("# %s must be%s: %s\n",
+                        $attr,
+                        ($oattr->mandatory ? '' : ' empty or either'),
+                        join(', ', @allowed)
+                    );
+                }
+                my @vals = ref $val ? @{ $val } : $val;
+                foreach (@vals) {
+                    $_ ||= '';
+                    s/\r?\n/\\n/g;
+                    $dump .= sprintf("%s%s:%s\n", 
+                        $oattr->ro ? '# (ro) ' : '',
+                        $attr, $_ ? " $_" : '');
+                }
+            }
+        } else {
+            if (my @allowed = $base->obj_attr_allowed_values($otype, $attr)) {
+                $dump .= sprintf("# %s must be empty or either: %s\n",
+                    $attr,
+                    join(', ', @allowed)
+                );
+            }
+            $dump .= sprintf("%s%s: %s\n", 
+                $oattr->ro ? '# (ro) ' : '',
+                $attr, '');
+        }
+    }
+    return $dump;
+}
+
+=head2 ReportChange($changetype, $message, @args)
+
+Possible per database way to log changes
+
+=cut
+
+sub ReportChange {
+    my ($self, $changetype, $message, @args) = @_;
+
+    $self->base->ReportChange(
+        $self->type,
+        $self->id,
+        $self->Iid,
+        $changetype, $message, @args
+    )
+}
+
+1;
+
+__END__
+
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases>
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin.ipsl.fr@localdomainE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql.pm	(revision 2119)
@@ -0,0 +1,1069 @@
+package LATMOS::Accounts::Bases::Sql;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases);
+use LATMOS::Accounts::Log;
+use DBI;
+use Crypt::RSA;
+use Crypt::RSA::Key::Public::SSH;
+use Crypt::RSA::Key::Private::SSH;
+use Crypt::RSA::Key::Public;
+use Crypt::RSA::Key::Private;
+use Crypt::Blowfish;
+use MIME::Base64;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+sub SCHEMA_VERSION { 28 };
+
+=head1 NAME
+
+LATMOS::Ad - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Accounts::Bases;
+  my $base = LATMOS::Accounts::Bases->new('unix');
+  ...
+
+=head1 DESCRIPTION
+
+Account base access over standard unix file format.
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 SCHEMA_VERSION
+
+Return the SQL schema version to use for this software version.
+
+=head2 new(%config)
+
+Create a new LATMOS::Ad object for windows AD $domain.
+
+domain / server: either the Ad domain or directly the server
+
+ldap_args is an optionnal list of arguments to pass to L<Net::LDAP>.
+
+=cut
+
+sub new {
+    my ($class, %config) = @_;
+    
+    my $base = {
+        db_conn => $config{db_conn},
+    };
+
+    bless($base, $class);
+}
+
+sub DESTROY {
+    my ($self) = @_;
+    $self->{_db} && $self->{_db}->rollback;
+}
+
+=head2 db
+
+Return a L<DBI> handle over database, load it if need.
+
+=cut
+
+sub db {
+    my ($self) = @_;
+
+    if ($self->{_db} && $self->{_db}->ping) {
+        return $self->{_db};
+    } else {
+        $self->{_db} = DBI->connect_cached(
+            'dbi:Pg:' . $self->{db_conn},
+            undef, undef,
+            {
+                RaiseError => 0,
+                AutoCommit => 0,
+                PrintWarn => 1,
+                PrintError => 1,
+                ($self->config('no_pg_utf8') ? (pg_enable_utf8 => 0) : ()),
+            }
+        ) or do {
+            $self->log(LA_ERR, "Cannot connect to database: %s", $DBI::errstr);   
+            return;
+        };
+        $self->{_db}->do(q{set DATESTYLE to 'DMY'});
+        $self->log(LA_DEBUG, 'New connection to DB');
+
+        {
+            my $sv = $self->get_global_value('schema_version') || 1;
+            if ($sv < SCHEMA_VERSION) {
+                require LATMOS::Accounts::Bases::Sql::upgrade;
+                if ($self->SchemaUpgrade()) {
+                    $self->commit;
+                } else {
+                    $self->rollback;
+                    return;
+                }
+            }
+        }
+
+        foreach my $otype ($self->list_supported_objects) {
+            my %attrlist = map { $_ => 1 } $self->list_registered_attributes($otype);
+            foreach my $attribute ($self->list_canonical_fields($otype, 'r')) {
+                my $attr = $self->attribute($otype, $attribute);
+                $attr->{inline} and next;
+                $attr->{managed} and next;
+
+                if ($attrlist{$attribute}) {
+                } else {
+                    if($self->register_attribute($otype, $attribute, $attr->{comment})) {
+                        $self->log(LA_NOTICE, "Attr. $attribute for object type $otype registred");
+                    } else {
+                        $self->log(LA_ERR, "Can't register attribute $attribute");
+                        $self->{_db}->rollback;
+                        return;
+                    }
+                }
+            }
+        }
+
+        $self->{_db}->commit;
+        return $self->{_db};
+    }
+}
+
+=head2 load
+
+Read file and load data into memory
+
+=cut
+
+sub load {
+    my ($self) = @_;
+    if (!$self->db) { return 0 };
+
+    my $sv = $self->get_global_value('schema_version') || 1;
+    if ($sv < SCHEMA_VERSION) {
+        $self->log(LA_CRIT,
+            "Schema version %d found, %d is need, please update db using " .
+            "`la-sql-upgrade' tool for `%s' base",
+            $sv,
+            SCHEMA_VERSION,
+            $self->label,
+        );
+        # return;
+    }
+
+    1;
+}
+
+=head2 ListInternalObjects($otype)
+
+List objects flags as internal for type C<$otype>
+
+=cut
+
+sub ListInternalObjects {
+    my ($self, $otype) = @_;
+
+    my $pclass = $self->_load_obj_class($otype) or return;
+
+    # Object Alias: checking if object is alias, then returning it:
+    my $sth = $self->db->prepare_cached(
+        sprintf(q{select %s as k from %s where and internobject = true},
+            $self->db->quote_identifier($pclass->_key_field),
+            $self->db->quote_identifier($pclass->_object_table),
+        ),
+    );
+    $sth->execute();
+    my @list;
+    while (my $res = $sth->fetchrow_hashref) {
+       push(@list, $_->{k});
+   }
+   return(@list);
+}
+
+=head2 GetRawObject($otype, $id)
+
+Return an object even it is internal, alias are not follow and even
+unexported object are returned
+
+This function must be used only for maintenance operation.
+
+=cut
+
+sub GetRawObject {
+    my ($self, $otype, $id) = @_;
+
+    my $pclass = $self->_load_obj_class($otype) or return;
+
+    return $self->SUPER::get_object($otype, $id);
+}
+
+sub _derefObject {
+    my ($self, $otype, $oalias) = @_;
+    $oalias or return;
+    if (my ($aliasotype, $aliasoname, $aliasattr) = $oalias =~ m/^([^\/]+)\.([^\.]+)\.(.*)$/) {
+        my $attribute = $self->attribute($aliasotype, $aliasattr) or do {
+            $self->log(LA_DEBUG, "Oalias %s (%s): can fetch attibute %s/%s",
+                $otype, $oalias, $aliasotype, $aliasattr);
+            return;
+        };
+        my $refotype = $attribute->reference or do {
+            $self->log(LA_DEBUG, "Oalias %s (%s): Attribute does not reference an object",
+                $otype, $oalias);
+            return;
+        };
+        $refotype eq $otype or do {
+            $self->log(LA_DEBUG, "Oalias %s (%s): Attribute does not reference same object type",
+                $otype, $oalias, $refotype);
+            return;
+        };
+        my $robj = $self->get_object($aliasotype, $aliasoname) or do {
+            $self->log(LA_DEBUG, "Oalias %s (%s): can fetch object %s/%s",
+                $otype, $oalias, $aliasotype, $aliasoname);
+            return;
+        };
+        my $rvalue = $robj->get_attributes($aliasattr) or do {
+            $self->log(LA_DEBUG, "Oalias %s (%s): attribute value is empty",
+                $otype, $oalias);
+            return;
+        };
+        return $self->get_object($refotype, $rvalue);
+    } else {
+        return $self->get_object($otype, $oalias);
+    }
+}
+
+sub get_object {
+    my ($self, $otype, $id) = @_;
+
+    my $pclass = $self->_load_obj_class($otype) or return;
+
+    # Object Alias: checking if object is alias, then returning it:
+    my $sth = $self->db->prepare_cached(
+        sprintf(q{select oalias from %s where %s = ? and internobject = false %s},
+            $self->db->quote_identifier($pclass->_object_table),
+            $self->db->quote_identifier($pclass->_key_field),
+            ($self->{wexported} ? '' : 'and exported = true'),
+        ),
+    );
+    $sth->execute($id);
+    my $res = $sth->fetchrow_hashref;
+    $sth->finish;
+    if (my $oalias = $res->{oalias}) {
+        # Cross reference over object/attribute
+        $self->_derefObject($otype, $oalias);
+    } else {
+        return $self->SUPER::get_object($otype, $id);
+    }
+}
+
+=head2 getObjectFromOKey ($okey)
+
+Return the object from the db internal key
+
+=cut
+
+sub getObjectFromOKey {
+    my ($self, $okey) = @_;
+
+    my $findobj = $self->{_db}->prepare_cached(q{
+        select * from objects_table where ikey = ?
+    });
+
+    $findobj->execute($okey);
+
+    my $res = $findobj->fetchrow_hashref;
+    $findobj->finish;
+
+    if ($res) {
+        return $self->get_object($res->{relname}, $res->{name});
+    } else {
+        return;
+    }
+}
+
+sub _sync_dyn_group {
+    my ($self) = @_;
+
+    my @groups = $self->search_objects('group', 'autoMemberFilter=*');
+
+    my $res = 0;
+    foreach (@groups) {
+        my $g = $self->get_object('group', $_) or next;
+        $res += $g->populate_dyn_group;
+    }
+
+    $self->log(LA_DEBUG, "Group Dyn res %d", $res);
+    $res
+}
+
+sub _sync_dyn_aliases {
+    my ($self) = @_;
+
+    my @groups = $self->search_objects('aliases', 'autoMemberFilter=*');
+
+    my $res = 0;
+    foreach (@groups) {
+        my $g = $self->get_object('aliases', $_) or next;
+        $res += $g->populate_dyn_aliases;
+    }
+
+    $self->log(LA_DEBUG, "Aliases Dyn res %d", $res);
+    $res
+}
+
+=head2 PopulateDynData
+
+Recomputate dynamics attributes (autoMembersFilters) if need
+
+=cut
+
+sub PopulateDynData {
+    my ($self) = @_;
+
+    $self->temp_switch_unexported(sub {
+
+        foreach (1 .. 5) {
+            $self->log(LA_DEBUG, "%d loop for PopulateDynData", $_);
+            my $res = 0;
+            $res += $self->_sync_dyn_group   || 0;
+            $res += $self->_sync_dyn_aliases || 0;
+
+            if ($res == 0) {
+                last;
+            }
+        }
+
+    }, 0);
+
+    return 1;
+}
+
+sub _commit {
+    my ($self) = @_;
+
+    $self->PopulateDynData;
+
+    if ($ENV{LA_NO_COMMIT}) {
+        $self->log(LA_DEBUG, 'DB::COMMIT (ignore due to LA_NO_COMMIT)');
+        return 1;
+    } else {
+        $self->log(LA_DEBUG, 'DB::COMMIT');
+    }
+    $self->{__cache} = undef;
+    $self->db->commit;
+}
+
+sub _rollback {
+    my ($self) = @_;
+    if ($ENV{LA_NO_COMMIT}) {
+        $self->log(LA_DEBUG, 'DB::ROLLBACK (ignore due to LA_NO_COMMIT)');
+        return 1
+    } else {
+        $self->log(LA_DEBUG, 'DB::ROLLBACK');
+    }
+    $self->{__cache} = undef;
+    $self->db->rollback;
+}
+
+sub list_supported_objects {
+    my ($self, @otype) = @_;
+    $self->SUPER::list_supported_objects(qw(site), @otype);
+}
+
+# For SQL listRealObjects != list_objects
+sub listRealObjects {
+    my ($self, $otype) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    $pclass->listReal($self);
+}
+
+sub current_rev {
+    my ($self) = @_;
+    my $sth = $self->db->prepare_cached(
+        q{select max(rev) from revisions}
+    );
+    $sth->execute;
+    my $res = $sth->fetchrow_hashref;
+    $sth->finish;
+    return ($res->{max});
+} 
+
+sub authenticate_user {
+    my ($self, $username, $passwd) = @_;
+    $username or return;
+    my $uobj = $self->get_object('user', $username) or do {
+        la_log(LA_ERR, "Cannot authenticate non existing SQL user $username");
+        return;
+    };
+
+    if ($self->attribute('user', 'exported')) {
+        if (!$uobj->_get_c_field('exported')) {
+            la_log(LA_ERR, "User $username found but currently unexported");
+            return;
+        }
+    }
+
+    $self->SUPER::authenticate_user($username, $passwd);
+}
+
+=head1 SPECIFICS FUNCTIONS
+
+=head2 GetAlias($base, $id)
+
+Return object having id C<$id> only if it is an object alias
+
+=cut
+
+sub GetAlias {
+    my ($self, $otype, $id) = @_;
+
+    my $pclass = $self->_load_obj_class($otype) or return;
+
+    # Object Alias: checking if object is alias, then returning it:
+    my $sth = $self->db->prepare_cached(
+        sprintf(q{select oalias from %s where %s = ? and oalias IS NOT NULL and internobject = false %s},
+            $self->db->quote_identifier($pclass->_object_table),
+            $self->db->quote_identifier($pclass->_key_field),
+            ($self->{wexported} ? '' : 'and exported = true'),
+        ),
+    );
+    $sth->execute($id);
+    my $res = $sth->fetchrow_hashref;
+    $sth->finish;
+    if ($res) {
+        return $self->SUPER::get_object($otype, $id);
+    } else {
+        return;
+    }
+}
+
+=head2 CreateAlias($otype, $name, $for)
+
+Create an object alias named C<$name> for ovbject C<$for>
+
+=cut
+
+sub CreateAlias {
+    my ($self, $otype, $name, $for) = @_;
+
+    my $pclass = $self->_load_obj_class($otype) or return;
+
+    $for or die "Cannot create alias without giving object to point";
+
+    my $res = $pclass->CreateAlias($self, $name, $for);
+
+    if ($res) {
+        $self->ReportChange(
+            $otype,
+            $name,
+            $pclass->_get_ikey($self, $name),
+            'Create', "Alias %s %s => %s", $otype, $name, $for
+        );
+        $self->log(LA_DEBUG, "Alias $otype $name => $for created");
+        my $oalias = $self->GetAlias($otype, $name);
+        $oalias->_update_aliases_ptr();
+        return 1;
+    } else {
+        $self->log(LA_ERR, "Error when creating alias $otype $name");
+        return;
+    }
+}
+
+=head2 RenameAlias($otype, $name, $to)
+
+Rename an object alias
+
+=cut
+
+sub RenameAlias {
+    my ($self, $otype, $name, $to) = @_;
+
+    my $pclass = $self->_load_obj_class($otype) or return;
+
+    my $obj = $self->GetAlias($otype, $name) or do {
+        $self->log('Cannot get alias %s/%s for removal', $otype, $name);
+        return;
+    };
+
+    my $sth = $self->db->prepare_cached(sprintf(
+        'UPDATE %s SET %s = ? WHERE %s = ?',
+        $self->db->quote_identifier($pclass->_key_field),
+        $self->db->quote_identifier($pclass->_object_table),
+        $self->db->quote_identifier($pclass->_key_field),
+    ));
+
+    my $res = $sth->execute($to, $name);
+
+    return $res;
+}
+
+=head2 RemoveAlias($otype, $name, $for)
+
+Create an object alias named C<$name> for ovbject C<$for>
+
+=cut
+
+sub RemoveAlias {
+    my ($self, $otype, $name) = @_;
+
+    my $pclass = $self->_load_obj_class($otype) or return;
+
+    my $obj = $self->GetAlias($otype, $name) or do {
+        $self->log('Cannot get alias %s/%s for removal', $otype, $name);
+        return;
+    };
+
+    if ($obj->_get_attributes('internobject')) {
+        # Cannot happend: internal are not fetchable
+        $self->log(LA_ERR,'Cannot delete %s/%s: is an internal object', $pclass->type, $name);
+        return;
+    }
+    if ($obj->_get_attributes('nodelete')) {
+        $self->log(LA_ERR,'Cannot delete %s/%s: is write protected', $pclass->type, $name);
+        return;
+    }
+
+    my $id = $obj->Iid;
+
+    my $sth = $self->db->prepare_cached(sprintf(
+        'DELETE FROM %s WHERE %s = ?',
+        $self->db->quote_identifier($pclass->_object_table),
+        $self->db->quote_identifier($pclass->_key_field),
+    ));
+
+    $obj->_update_aliases_ptr;
+    my $res = $sth->execute($name);
+
+    if ($res) {
+        $self->ReportChange(
+            $otype,
+            $name,
+            $id,
+            'Delete', "Alias %s %s deleted", $otype, $name
+        );
+        $self->log(LA_DEBUG, "Alias $otype $name removed");
+        return 1;
+    } else {
+        $self->log(LA_ERR, "Error when removing alias $otype $name");
+        return;
+    }
+}
+
+=head2 get_global_value ($varname)
+
+Return global value set into base
+
+=cut
+
+sub get_global_value {
+    my ($self, $varname) = @_;
+
+    my $sth = $self->db->prepare_cached(q{
+        select val from settings where varname = ?
+        });
+    $sth->execute($varname);
+    my $res = $sth->fetchrow_hashref;
+    $sth->finish;
+    $res->{val}
+}
+
+=head2 set_global_value ($varname, $value)
+
+Set global value.
+
+=cut
+
+sub set_global_value {
+    my ($self, $varname, $value) = @_;
+    my $sth = $self->db->prepare(q{
+        update settings set val = ? where varname = ?
+        });
+    $sth->execute($value, $varname) == 0 and do {
+        my $sth2 = $self->db->prepare(q{
+            insert into settings (val, varname) values (?,?)
+            });
+        $sth2->execute($value, $varname);
+    };
+}
+
+=head2 del_global_value ($varname)
+
+Delete global value from base
+
+=cut
+
+sub del_global_value {
+    my ($self, $varname) = @_;
+
+    my $sth = $self->db->prepare_cached(q{
+        delete from settings where varname = ?
+        });
+    return $sth->execute($varname);
+}
+
+=head2 generate_rsa_key ($password)
+
+Return public and private peer rsa keys
+
+=cut
+
+sub generate_rsa_key {
+    my ($self, $password) = @_;
+
+    my $rsa = new Crypt::RSA ES => 'PKCS1v15';
+    my ($public, $private) = $rsa->keygen (
+        Identity  => 'LATMOS-Accounts',
+        Size      => 2048,
+        Password  => $password,
+        Verbosity => 0,
+    ) or die $rsa->errstr(); # TODO avoid die
+    return ($public, $private);
+}
+
+=head2 private_key ($password)
+
+Load and return private rsa key
+
+=cut
+
+sub private_key {
+    my ($self, $password) = @_;
+    my $base = $self;
+    my $serialize = $base->get_global_value('rsa_private_key') or return;
+    my $string = decode_base64($serialize);
+    my $privkey = $string =~ /^SSH PRIVATE KEY FILE/
+        ? Crypt::RSA::Key::Private::SSH->new
+        : Crypt::RSA::Key::Private->new;
+    $privkey = $privkey->deserialize(
+        String => [ $string ],
+        Password => $password
+    );
+    $privkey->reveal( Password => $password );
+    $privkey;
+}
+
+=head2 get_rsa_password
+
+Return hash with peer username => encryptedPassword
+
+=cut
+
+sub get_rsa_password {
+    my ($self) = @_;
+    my $base = $self;
+    my $sth = $base->db->prepare(q{
+        select "name", value from "user" join user_attributes_base
+        on "user".ikey = user_attributes_base.okey
+        where user_attributes_base.attr = 'encryptedPassword'
+    });
+    $sth->execute;
+    my %users;
+    while (my $res = $sth->fetchrow_hashref) {
+        $users{$res->{name}} = $res->{value};
+    }
+    %users
+}
+
+=head2 store_rsa_key ($public, $private)
+
+Store public and private RSA key info data base
+
+=cut
+
+sub store_rsa_key {
+    my ($self, $public, $private) = @_;
+    my $base = $self;
+    $private->hide;
+    $base->set_global_value('rsa_private_key',
+        encode_base64($private->serialize));
+    $base->set_global_value('rsa_public_key',
+        $public->serialize);
+    return;
+}
+
+=head2 find_next_expire_users ($expire)
+
+Search user expiring in C<$expire> delay
+
+=cut
+
+sub find_next_expire_users {
+    my ($self, $expire) = @_;
+
+    my $sth= $self->db->prepare(q{
+        select name from "user" where
+            expire < now() + ?::interval
+            and expire > now()
+            and expire is not null
+            and internobject = false
+            } . ($self->{wexported} ? '' : 'and exported = true') . q{
+            order by expire
+        }
+    );
+    $sth->execute($expire || '1 month');
+    my @users;
+    while (my $res = $sth->fetchrow_hashref) {
+        push(@users, $res->{name});
+    }
+    @users
+}
+
+=head2 find_expired_users ($expire)
+
+Return list of user going to expires in C<$expire> delay
+
+=cut
+
+sub find_expired_users {
+    my ($self, $expire) = @_;
+
+    my $sth= $self->db->prepare(q{
+        select name from "user" where
+            expire < now() - ?::interval
+            and expire is not null
+            and internobject = false
+        } . ($self->{wexported} ? '' : 'and exported = true') . q{
+            order by expire
+        }
+    );
+    $sth->execute($expire || '1 second');
+    my @users;
+    while (my $res = $sth->fetchrow_hashref) {
+        push(@users, $res->{name});
+    }
+    @users
+}
+
+=head2 rename_nethost ($nethostname, $to, %config)
+
+Facility function to rename computer to new name
+
+=cut
+
+sub rename_nethost {
+    my ($self, $nethostname, $to, %config) = @_;
+    {
+        my $obj = $self->get_object('nethost', $nethostname) or do {
+            $self->log(LA_ERR, 'Unable to rename non exisant host %s', $nethostname);
+            return;
+        };
+        $obj->_delAttributeValue(cname => $to);
+    }
+    $self->rename_object('nethost', $nethostname, $to) or return;
+    if ($config{'addcname'}) {
+        my $obj = $self->get_object('nethost', $to);
+        $obj->_addAttributeValue(cname => $nethostname);
+    }
+    return 1;
+}
+
+=head2 nethost_exchange_ip ($ip1, $ip2)
+
+Exchange ip1 with ip2 in base
+
+=cut
+
+sub nethost_exchange_ip {
+    my ($self, $ip1, $ip2) = @_;
+    my ($obj1, $obj2);
+    if (my ($host1) = $self->search_objects('nethost', "ip=$ip1")) {
+        $obj1 = $self->get_object('nethost', $host1);
+    } else {
+        $self->log(LA_ERR, "Cannot find host having $ip1");
+        return;
+    }
+    if (my ($host2) = $self->search_objects('nethost', "ip=$ip2")) {
+        $obj2 = $self->get_object('nethost', $host2);
+    } else {
+        $self->log(LA_ERR, "Cannot find host having $ip2");
+        return;
+    }
+    if ($obj1->id eq $obj2->id) {
+        $self->log(LA_ERR, "Both ip belong to same host (%s)", $obj1->id);
+        return;
+    }
+
+    $self->log(LA_NOTICE, "Exchanging IP between %s and %s", $obj1->id, $obj2->id);
+    $obj1->delAttributeValue('ip', $ip1) or return;
+    $obj2->delAttributeValue('ip', $ip2) or return;
+    $obj1->addAttributeValue('ip', $ip2) or return;
+    $obj2->addAttributeValue('ip', $ip1) or return;
+    return 1;
+}
+
+=head1 ATTRIBUTES FUNCTIONS
+
+=cut
+
+sub obj_attr_allowed_values {
+    my ($self, $otype, $attr) = @_;
+    if (my @values = $self->SUPER::obj_attr_allowed_values("$otype.$attr", 'allowed')) {
+        return @values;
+    } else {
+        $self->ListAttrValue($otype, $attr);
+    }
+}
+
+=head2 ListAttrValue($otype, $attribute)
+
+List values allow for an attribute set into SQL database
+
+=cut
+
+sub ListAttrValue {
+    my ($self, $otype, $attr) = @_;
+
+    my @sqlvalues;
+    my $getAllow = $self->db->prepare_cached(q{
+        SELECT * FROM attributes_values WHERE otype = ? AND attributes = ?
+        ORDER BY "value"
+    });
+    $getAllow->execute($otype, $attr);
+    while (my $res = $getAllow->fetchrow_hashref) {
+        push(@sqlvalues, $res->{value});
+    }
+    return @sqlvalues;
+}
+
+=head2 AddAttrValue($otype, $attr, @values)
+
+Add given values to allowed attribute list
+
+=cut
+
+sub AddAttrValue {
+    my ($self, $otype, $attr, @values) = @_;
+    
+    my $addAllow = $self->db->prepare_cached(q{
+        INSERT INTO attributes_values (otype, attributes, "value") values (?,?,?)
+    });
+    
+    foreach my $value (@values) {
+        if ($addAllow->execute($otype, $attr, $value)) {
+        } else {
+            $self->rollback;
+            return;
+        }
+    }
+
+    return 1;
+}
+
+=head2 DelAttrValue
+
+Delete a
+
+=cut
+
+sub DelAttrValue {
+    my ($self, $otype, $attr, @values) = @_;
+
+    if (@values) {
+        my $delAllow = $self->db->prepare_cached(q{
+            DELETE FROM attributes_values WHERE otype = ? and attributes = ? and "value" = ?
+        });
+
+        foreach my $value (@values) {
+
+            if ($delAllow->execute($otype, $attr, $value)) {
+            } else {
+                $self->rollback;
+                return;
+            }
+
+        }
+        return 1;
+    } else {
+        my $delAllow = $self->db->prepare_cached(q{
+            DELETE FROM attributes_values WHERE otype = ? and attributes = ?
+        });
+        if ($delAllow->execute($otype, $attr)) {
+            return 1;
+        } else {
+            $self->rollback;
+            return;
+        }
+    }
+}
+
+=head2 register_attribute ($otype, $attribute, $comment)
+
+Register a new attribute in base
+
+=cut
+
+sub register_attribute {
+    my ($self, $otype, $attribute, $comment) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    $pclass->register_attribute($self, $attribute, $comment);
+}
+
+=head2 is_registered_attribute ($otype, $attribute)
+
+Return true is attribute already exists
+
+=cut
+
+sub is_registered_attribute {
+    my ($self, $otype, $attribute) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    $pclass->is_registered_attribute($self, $attribute);
+}
+
+=head2 list_registered_attributes ($otype)
+
+List all regiestered attribute
+
+=cut
+
+sub list_registered_attributes {
+    my ($self, $otype) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    if ($pclass->_has_extended_attributes) {
+        return $pclass->list_registered_attributes($self);
+    } else {
+        return ();
+    }
+}
+
+=head2 get_attribute_comment ($otype, $attribute)
+
+Return the comment associated to attribute
+
+=cut
+
+sub get_attribute_comment {
+    my ($self, $otype, $attribute) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    $pclass->get_attribute_comment($self, $attribute);
+}
+
+=head2 set_attribute_comment ($otype, $attribute, $comment)
+
+Set comment to attribute
+
+=cut
+
+sub set_attribute_comment {
+    my ($self, $otype, $attribute, $comment) = @_;
+    my $pclass = $self->_load_obj_class($otype) or return;
+    $pclass->set_attribute_comment($self, $attribute, $comment);
+}
+
+sub ReportChange {
+    my ($self, $otype, $name, $ref, $changetype, $message, @args) = @_;
+
+    my $sthmodifiedby = $self->db->prepare(q{
+        UPDATE objects set modifiedby = ? where ikey = ?
+    });
+
+    $sthmodifiedby->execute(
+        $self->user || '@Console',
+        $ref,
+    );
+
+    my $sth = $self->db->prepare(q{
+        INSERT into objectslogs (ikey, irev, otype, name, changetype, username, message)
+        VALUES (?,?,?,?,?,?,?)
+        });
+
+    $sth->execute(
+        $ref,
+        $self->current_rev,
+        $otype,
+        $name,
+        $changetype,
+        $self->user || '@Console',
+        sprintf($message, @args),
+    );
+}
+
+=head2 getobjectlogs($otype, $name)
+
+Return logs for object type C<$otype> having C<$name>.
+
+=cut 
+
+sub getobjectlogs {
+    my ($self, $otype, $name) = @_;
+
+    my $sth = $self->db->prepare(q{
+        select ikey from objectslogs where
+            otype = ? and
+            name  = ?
+        group by ikey
+    });
+    $sth->execute($otype, $name);
+    my @ids;
+    while (my $res = $sth->fetchrow_hashref) {
+        push(@ids, $res->{ikey});
+    }
+    @ids or return;
+
+    my $sth2 = $self->db->prepare(sprintf(
+        q{
+            select * from objectslogs where ikey IN (%s)
+            order by logdate asc
+        },
+        join(',', ('?') x scalar(@ids))
+    ));
+
+    $sth2->execute(@ids);
+    my @logs;
+    while (my $res = $sth2->fetchrow_hashref) {
+        push(@logs, $res);
+    }
+
+    return @logs;
+}
+
+=head2 getlogs
+
+Return logs for last year
+
+=cut
+
+sub getlogs {
+    my ($self) = @_;
+    my $sth2 = $self->db->prepare(
+        q{
+            select * from objectslogs 
+            where logdate > now() - '1 year'::interval 
+            order by logdate asc
+        },
+    );
+
+    $sth2->execute();
+    my @logs;
+    while (my $res = $sth2->fetchrow_hashref) {
+        push(@logs, $res);
+    }
+
+    return @logs;
+}
+
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Address.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Address.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Address.pm	(revision 2119)
@@ -0,0 +1,374 @@
+package LATMOS::Accounts::Bases::Sql::Address;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Sql::objects);
+use LATMOS::Accounts::I18N;
+
+our $VERSION = (q$Rev: 594 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::Address - Physical office Adress Support
+
+=head1 DESCRIPTION
+
+C<Address> objects allow to store user's several office addresses.
+
+Notice most of other bases (Ldap, ...) support only one address.
+
+=cut
+
+sub stringify {
+    my ($self) = @_;
+
+    my $user = $self->base->get_object('user', $self->_get_c_field('user'))
+        or return $self->id;
+
+    return join(', ', grep { $_ }
+        (
+            $user,
+            $self->get_c_field('l'),
+        )
+    );
+}
+
+sub _object_table { 'address' }
+
+sub _key_field { 'name' }
+
+sub _has_extended_attributes { 1 }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+
+    $class->SUPER::_get_attr_schema($base,
+        {
+            name =>         { inline => 1, },
+            exported =>     { inline => 1, },
+            user =>         {
+                inline => 1,
+                reference => 'user',
+                label => l('User'),
+            },
+            site =>         {
+                reference => 'site',
+                label => l('Site'),
+            },
+            sn =>           {
+                managed => 1,
+                ro => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+                    my $user = $self->base->get_object('user', $self->_get_c_field('user'))
+                        or return;
+                    return $user->_get_c_field($attr->name);
+                },
+                label => l('Last name'),
+            },
+            mail =>         {
+                managed => 1,
+                ro => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+                    my $user = $self->base->get_object('user', $self->_get_c_field('user'))
+                        or return;
+                    return $user->_get_c_field($attr->name);
+                },
+                label => l('Email'),
+            },
+            givenName => {
+                managed => 1,
+                ro => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+                    my $user = $self->base->get_object('user', $self->_get_c_field('user'))
+                        or return;
+                    return $user->_get_c_field($attr->name);
+                },
+                label => l('First name'),
+            },
+            postalAddress => {
+                managed => 1,
+                ro => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+                    my $bp = $self->_get_c_field('postOfficeBox');
+                    return join("\n", grep { $_ } (
+                            $self->_get_c_field('o'),
+                            $self->_get_c_field('streetAddress'),
+                            ($bp ? 'BP ' . $bp : ''),
+                            join(' ', grep { $_ } (
+                                    $self->_get_c_field('postalCode'),
+                                    $self->_get_c_field('l'),)),
+                            join(' ', grep { $_ } (
+                                    $self->_get_c_field('st'),
+                                    $self->_get_c_field('co'),),
+                            )
+                        )
+                    );
+                },
+                label => l('Postal address'),
+            },
+            displayName =>  {
+                managed => 1,
+                ro => 1,
+                get => sub {
+                    my $self = $_[0]->object;
+                    return join(' ', grep { $_ } (
+                            $self->_get_c_field('givenName'),
+                            $self->_get_c_field('sn'),
+                        )
+                    );
+                },
+                label => l('Name'),
+            },
+            co => {
+                managed => 1,
+                ro => sub { $_[0] && $_[0]->get_attributes('site') ? 1 : 0 },
+                get => sub {
+                    my ($self) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        if (my $osite = $self->base->get_object('site', $site)) {
+                            return $osite->get_attributes($self->name);
+                        }
+                    }
+                    return $self->object->get_field($self->name);
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        return 1;
+                    } else {
+                        return $self->object->set_fields($self->name, $data)
+                    }
+                },
+                label => l('Country'),
+            },
+            l => {
+                managed => 1,
+                ro => sub { $_[0] && $_[0]->get_attributes('site') ? 1 : 0 },
+                get => sub {
+                    my ($self) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        if (my $osite = $self->base->get_object('site', $site)) {
+                            return $osite->get_attributes($self->name);
+                        }
+                    }
+                    return $self->object->get_field($self->name);
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        return 1;
+                    } else {
+                        return $self->object->set_fields($self->name, $data)
+                    }
+                },
+                label => l('City'),
+            },
+            postalCode =>   {
+                managed => 1,
+                ro => sub { $_[0] && $_[0]->get_attributes('site') ? 1 : 0 },
+                get => sub {
+                    my ($self) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        if (my $osite = $self->base->get_object('site', $site)) {
+                            return $osite->get_attributes($self->name);
+                        }
+                    }
+                    return $self->object->get_field($self->name);
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        return 1;
+                    } else {
+                        return $self->object->set_fields($self->name, $data)
+                    }
+                },
+                label => l('Postal code'),
+            },
+            streetAddress => {
+                managed => 1,
+                formtype => 'TEXTAREA',
+                ro => sub { $_[0] && $_[0]->get_attributes('site') ? 1 : 0 },
+                get => sub {
+                    my ($self) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        if (my $osite = $self->base->get_object('site', $site)) {
+                            return $osite->get_attributes($self->name);
+                        }
+                    }
+                    return $self->object->get_field($self->name);
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        return 1;
+                    } else {
+                        return $self->object->set_fields($self->name, $data)
+                    }
+                },
+                label => l('Street'),
+            },
+            postOfficeBox => {
+                managed => 1,
+                ro => sub { $_[0] && $_[0]->get_attributes('site') ? 1 : 0 },
+                get => sub {
+                    my ($self) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        if (my $osite = $self->base->get_object('site', $site)) {
+                            return $osite->get_attributes($self->name);
+                        }
+                    }
+                    return $self->object->get_field($self->name);
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        return 1;
+                    } else {
+                        return $self->object->set_fields($self->name, $data)
+                    }
+                },
+                label => l('Post office box'),
+            },
+            st => {
+                managed => 1,
+                ro => sub { $_[0] && $_[0]->get_attributes('site') ? 1 : 0 },
+                get => sub {
+                    my ($self) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        if (my $osite = $self->base->get_object('site', $site)) {
+                            return $osite->get_attributes($self->name);
+                        }
+                    }
+                    return $self->object->get_field($self->name);
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        return 1;
+                    } else {
+                        return $self->object->set_fields($self->name, $data)
+                    }
+                },
+                label => l('Street'),
+            },
+            facsimileTelephoneNumber => {
+                managed => 1,
+                ro => sub { $_[0] && $_[0]->get_attributes('site') ? 1 : 0 },
+                get => sub {
+                    my ($self) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        if (my $osite = $self->base->get_object('site', $site)) {
+                            return $osite->get_attributes($self->name);
+                        }
+                    }
+                    return $self->object->get_field($self->name);
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        return 1;
+                    } else {
+                        return $self->object->set_fields($self->name, $data)
+                    }
+                },
+                label => l('Fax number'),
+            },
+            o =>            {
+                managed => 1,
+                ro => sub { $_[0] && $_[0]->get_attributes('site') ? 1 : 0 },
+                get => sub {
+                    my ($self) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        if (my $osite = $self->base->get_object('site', $site)) {
+                            return $osite->get_attributes($self->name);
+                        }
+                    }
+                    return $self->object->get_field($self->name);
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                    if (my $site = $self->object->get_attributes('site')) {
+                        return 1;
+                    } else {
+                        return $self->object->set_fields($self->name, $data)
+                    }
+                },
+                label => l('Company'),
+            },
+            isMainAddress => {
+                formtype => 'CHECKBOX',
+                label => l('Main address'),
+            },
+            contactOnly => {
+                formtype => 'CHECKBOX',
+                label => l('For contact only'),
+            },
+            active => { ro => 1 },
+            telephoneNumber => {
+                label => l('Phone number'),
+            },
+            physicalDeliveryOfficeName => {
+                label => l('Office'),
+            },
+            otherTelephone => { },
+            ipPhone => { },
+            homePhone => { },
+            description => {
+                label => l('Description'),
+            },
+            expire    => {
+                inline => 1,
+                formtype => 'DATE',
+                label => l('Expire'),
+            },
+        }
+    );
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+    $data{user} or return;
+    my $user = $base->get_object('user', $data{user});
+    $user or return;
+    if (!$user->get_c_field('mainaddress')) {
+        $data{isMainAddress} = 1;
+    }
+    $data{exported} = $user->get_attributes('exported');
+    $class->SUPER::_create($base, $id, %data);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>
+
+L<LATMOS::Accounts::Bases::Sql::Onlyaddress>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Aliases.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Aliases.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Aliases.pm	(revision 2119)
@@ -0,0 +1,345 @@
+package LATMOS::Accounts::Bases::Sql::Aliases;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Sql::objects);
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::I18N;
+
+our $VERSION = (q$Rev: 341 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::Aliases - Mail alias object
+
+=cut
+
+sub _object_table { 'aliases' }
+
+sub _key_field { 'name' }
+
+sub _has_extended_attributes { 1 }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+
+    
+    my $recurs = sub {
+        my ($attr) = @_;
+        my ($self) = $attr->object;
+        my $field = $attr->name; 
+
+        my $sth = $self->db->prepare_cached(q{
+            select name from aliases where array[lower($1)] <@
+            string_to_array(lower(array_to_string("forward", ',')), ',')
+            });
+        my @tocheck = ($field =~ /^sameforward$/
+            ? $self->get_attributes('forward')
+            : ($field =~ /^samedestination$/ 
+                ? $self->get_attributes('finalpoint')
+                : $self->id));
+        my @results;
+        my @already;
+        while (my $next = shift(@tocheck)) {
+            # endless loop detection
+            grep { $next eq $_ } @already and do {
+                push(@results, $next);
+                next;
+            };
+            push(@already, $next);
+            $sth->execute($next);
+            while (my $res = $sth->fetchrow_hashref) {
+                push(@tocheck, $res->{name});
+                $res->{name} eq $self->id and next;
+                push(@results, $res->{name});
+            }
+            $field =~ /^(parents|sameforward)$/ and last; # No recursive search
+        }
+        return scalar(@results) <= 1 ? $results[0] : \@results;
+    };
+
+    $class->SUPER::_get_attr_schema($base,
+        {
+            name            => {
+                ro => 1,
+            },
+            forward         => {
+                label => l('Forward'),
+                delayed => 1,
+                mandatory => 1,
+                multiple => 1,
+                monitored => 1,
+                set => sub {
+                    my ($self, $data) = @_;
+                    my @datas = $data
+                        ? (ref $data
+                            ? @$data
+                            : ($data))
+                        : ();
+                    my $sql = @datas
+                        ? '{' . join(',', map { $_ =~ s/\\/\\\\/g; '"' .$_.'"' } @datas) . '}'
+                        : undef;
+                    $base->db->do(
+                        q{UPDATE aliases SET forward = }
+                        . $base->db->quote($sql) . q{ where name = } .
+                        $base->db->quote($self->object->id)
+                    );
+                    return 1;
+                },
+                ro => sub {
+                    $_[0] && $_[0]->_get_c_field('autoMemberFilter') ? 1 : 0
+                },
+            },
+            rfc822MailMember => {
+                iname => 'forward',
+                hidden => 1,
+                ro => 1,
+                multiple => 1,
+            },
+            finalpoint      => {
+                ro => 1,
+                multiple => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $sth = $self->base->db->prepare_cached(q{
+                        select forward from aliases where lower(name) = lower(?)
+                        limit 1
+                        });
+                    my @res = ();
+                    my @next = @{ $self->object->get_field('forward') || [] };
+                    my %seen;
+                    while (my $next = shift(@next)) {
+                        # Avoiding loop:
+                        $seen{$next} and do {
+                            push(@res, $next);
+                            next;
+                        };
+                        $seen{$next} = 1;
+                        $sth->execute($next);
+                        if (my $res = $sth->fetchrow_hashref) {
+                            push(@next, @{ $res->{forward} });
+                        } else {
+                            push(@res, $next);
+                        }
+                    }
+                    $sth->finish;
+                    return @res ? \@res : undef;
+                },
+                label => l('Final recipient')
+            },
+            parents         => {
+                ro => 1,
+                get => $recurs,
+                label => l('Parents')
+            },
+            anyparents      => {
+                ro => 1,
+                get => $recurs,
+            },
+            sameforward     => {
+                ro => 1,
+                multiple => 1,
+                get => $recurs,
+            },
+            samedestination => {
+                ro => 1,
+                multiple => 1,
+                get => $recurs,
+            },
+            user            => {
+                ro => 1,
+                reference => 'user',
+                multiple => 1,
+                label => l('User'),
+            },
+            expire    => {
+                inline => 1,
+                formtype => 'DATE',
+                label => l('Expire'),
+            },
+            comment => {
+                label => l('Comment'),
+            },
+            autoMemberFilter => {
+                multiple => 1,
+                set => sub {
+                    my ($self, $data) = @_;
+                    $self->object->set_fields($self->name, $data) or return;
+                    $self->object->populate_dyn_aliases;
+                    return 1;
+                },
+                label => l('Automatics filters'),
+            },
+            autoFromSutype => {
+                multiple => 1,
+                set => sub {
+                    my ($self, $data) = @_;
+                    $self->object->set_fields($self->name, $data) or return;
+                    $self->object->populate_dyn_aliases;
+                    return 1;
+                },
+                label => l('From group type'),
+            },
+            autoExclude => {
+                multiple => 1,
+                reference => 'user',
+                label => l('User excluded'),
+            },
+        }
+    )
+}
+
+sub new {
+    my ($class, $base, $id) = @_;
+    my $sth = $base->db->prepare_cached(q{
+        select name from aliases where lower(name) = lower(?)
+    });
+    $sth->execute($id);
+    my $res = $sth->fetchrow_hashref;
+    $sth->finish;
+    if ($res) {
+        $class->SUPER::new($base, $res->{name});
+    } else {
+        return;
+    }
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+    $data{'forward'} = [ $data{'forward'} ] unless(ref $data{'forward'});
+    @{$data{'forward'}} = grep { $_ } @{$data{'forward'}};
+    my $sth = $base->db->prepare_cached(q{
+        select name from aliases where lower(name) = lower(?)
+        limit 1
+        });
+    $sth->execute($id);
+    my $res = $sth->fetchrow_hashref;
+    $sth->finish;
+    if ($res) {
+        $base->log(LA_ERR, "Aliases %s already exists as %s",
+            $id, $res->{name});
+        return;
+    } else {
+        return $class->SUPER::_create($base, $id, %data);
+    }
+}
+
+sub set_fields {
+    my ($self, %data) = @_;
+
+    if ($data{forward}) {
+        $data{forward} = ref $data{forward}
+            ? $data{forward}
+            : [ $data{forward} ];
+    }
+
+    $self->SUPER::set_fields(%data);
+}
+
+=head2 populate_dyn_aliases
+
+Synchronise aliase's forward according filter set into C<autoMemberFilter> attribute.
+
+=cut
+
+sub populate_dyn_aliases {
+    my ($self) = @_;
+
+
+    if (!$self->get_field('autoMemberFilter')
+     && !$self->get_field('autoFromSutype') ) {
+        return 0;
+    }
+
+    $self->base->log(LA_DEBUG,
+        "Populating aliases %s from autoMemberFilter attribute",
+        $self->id
+    );
+
+    my $listh = $self->base->db->prepare_cached(q{
+        SELECT forward FROM aliases WHERE ikey = ?
+    });
+
+    $listh->execute($self->Iid);
+    my $res = $listh->fetchrow_hashref;
+    $listh->finish;
+    my $currentForward = $res->{forward} || [];
+
+    my %email = $self->base->attributes_summary_by_object('user', 'mail');
+    my %users;
+
+    my $autoex = $self->get_field('autoExclude');
+    my %exclude = map { $_ => 1 } grep { $_  } (ref $autoex ? @$autoex : $autoex);
+
+    if (my $filter = $self->get_field('autoMemberFilter')) {
+        $users{$_} = 1 foreach( $self->base->search_objects(
+                'user',
+                'oalias=NULL',
+                ref $filter ? @{ $filter } : $filter)
+        );
+    }
+
+    if (my $filter = $self->get_field('autoFromSutype')) {
+        my @suTypeFilter = map { "sutype=$_" } (ref $filter ? @{ $filter } : $filter);
+        foreach my $group ($self->base->search_objects('group', @suTypeFilter)) {
+            foreach ($self->base->search_objects('user', 'oalias=NULL', "memberOf=$group")) {
+                $users{$_} = 1;
+            }
+        }
+    }
+
+    my %forward = ();
+
+    foreach my $user (keys %users) {
+        $exclude{$user} and next;
+        $email{$user} or next;
+        my $mail = $email{$user}->[0] or next;
+        $forward{$mail} = 1;
+    }
+
+    if (join(',', sort @$currentForward) eq join(',', sort keys %forward)) {
+        $self->base->log(
+            LA_DEBUG,
+            "No need to update alias %s for autoMemberFilter settings",
+            $self->id,
+        );
+        return 0;
+    }
+
+    my $sth = $self->base->db->prepare_cached(
+        'UPDATE aliases SET forward = ? WHERE ikey = ?'
+    );
+    if (!$sth->execute([ sort keys %forward ], $self->Iid)) {
+        $self->base->log(LA_ERR, "Cannot update forward for aliases %s: %s", $self->id, $sth->errstr());
+        return;
+    }
+
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>
+
+L<LATMOS::Accounts::Bases::Sql::Revaliases>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Employment.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Employment.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Employment.pm	(revision 2119)
@@ -0,0 +1,434 @@
+package LATMOS::Accounts::Bases::Sql::Employment;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Sql::objects);
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::I18N;
+use Date::Parse;
+use DateTime;
+use Date::Calc;
+
+our $VERSION = (q$Rev: 594 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::Address - Physical office Adress Support
+
+=head1 DESCRIPTION
+
+C<Address> objects allow to store user's several office addresses.
+
+Notice most of other bases (Ldap, ...) support only one address.
+
+=cut
+
+sub stringify {
+    my ($self) = @_;
+
+    my $user = $self->base->get_object('user', $self->_get_c_field('user'))
+        or return $self->id;
+
+    return join(', ', grep { $_ }
+        (
+            $user,
+            $self->get_c_field('firstday'),
+        )
+    );
+}
+
+sub _object_table { 'employment' }
+
+sub _key_field { 'name' }
+
+sub _has_extended_attributes { 1 }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+
+    my $attrs = {
+        name =>         { inline => 1, },
+        exported =>     { inline => 1, },
+        user =>         { inline => 1,
+            reference => 'user',
+            label => l('User'),
+        },
+        description => {
+            label => l('Description'),
+        },
+        firstday => {
+            inline => 1,
+            formtype => 'DATE',
+            monitored => 1,
+            label => l('Contract start'),
+        },
+        lastday => {
+            inline => 1,
+            formtype => 'DATE',
+            monitored => 1,
+            label => l('Contract end'),
+        },
+        length => {
+            ro => 1,
+            managed => 1,
+            get => sub {
+                my ($self) = @_;
+                my $lastday = $self->object->get_attributes('lastday') || DateTime->now->ymd('-');
+                my $firstday = $self->object->get_attributes('firstday');
+
+                my @fd = split('-', $firstday);
+                my @ld = split('-', $lastday);
+
+                return Date::Calc::Delta_Days(@fd, @ld) +1;
+            },
+            label => l('Length'),
+        },
+        lengthText => {
+            ro => 1,
+            managed => 1,
+            get => sub {
+                my ($self) = @_;
+                my $lastday = $self->object->get_attributes('lastday')|| DateTime->now->ymd('-');
+                {
+                    my $dtlast = DateTime->from_epoch(epoch => str2time($lastday));
+                    $dtlast->add(days => 1);
+                    $lastday = $dtlast->ymd('-');
+                }
+                my $firstday = $self->object->get_attributes('firstday');
+
+                return if ($firstday gt $lastday);
+
+                my @fd = split('-', $firstday) or return;
+                my @ld = split('-', $lastday)  or return;
+
+                my ($Dy,$Dm,$Dd) = Date::Calc::N_Delta_YMD(@fd, @ld);
+                return join(', ',
+                    ($Dy ? l('%d years', $Dy)  : ()),
+                    ($Dm ? l('%d months', $Dm) : ()),
+                    ($Dd ? l('%d days', $Dd)   : ()),
+                );
+            },
+            label => l('Length'),
+        },
+        'state' => {
+            managed => 1,
+            ro => 1,
+            get => sub {
+                my ($self) = @_;
+                my $now = DateTime->now;
+                if ($now->epoch < str2time($self->object->get_attributes('firstday'))) {
+                    return 1;
+                } elsif ( my $end = $self->object->get_attributes('lastday') ) {
+                    my $eend = str2time($end) + 86400;
+                    if ($now->epoch > $eend) {
+                        return -1;
+                    } else {
+                        return 0;
+                    }
+                } else {
+                    return 0;
+                }
+            },
+            label => l('State'),
+        },
+        contratType => {
+            reference => 'group',
+            can_values => sub {
+                $base->search_objects('group', 'sutype=contrattype')
+            },
+            monitored => 1,
+            label => l('Type of contract'),
+        },
+        managerContact => {
+            delayed => 1,
+            can_values => sub {
+                my %uniq = map { $_ => 1 } grep { $_ }
+                ($_[1] ? $_[1]->get_attributes('managerContact') : ()),
+                $base->search_objects('user', 'active=*');
+                sort keys %uniq;
+            },
+            reference => 'user',
+            label => l('Direct manager'),
+        },
+        department => {
+            reference => 'group',
+            can_values => sub {
+                $base->search_objects('group', 'sutype=dpmt')
+            },
+            monitored => 1,
+            label => l('Department'),
+        },
+        company => {
+            label => l('Company'),
+        },
+        employer => {
+            label => l('Employer'),
+        },
+        endcircuit    => {
+            formtype => 'DATE',
+            monitored => 1,
+            label => l('End of entrance circuit'),
+        },
+        hosted => {
+            formtype => 'CHECKBOX',
+            label => l('Hosted'),
+        },
+        requestId => {
+            label => l('Request id')
+        },
+        previous => {
+            ro => 1,
+            managed => 1,
+            get => sub {
+                my ($self) = @_;
+                my $find = $self->object->base->db->prepare_cached(q{
+                    SELECT name from employment where "user" = ?
+                        and (lastday is not null and lastday < ?)
+                        order by lastday desc
+                });
+                $find->execute(
+                    $self->object->get_field('user'),
+                    $self->object->get_field('firstday')
+                );
+                my $res = $find->fetchrow_hashref;
+                $find->finish;
+                return $res->{name};
+            },
+            label => l('Previous'),
+        },
+        'next' => {
+            ro => 1,
+            managed => 1,
+            get => sub {
+                my ($self) = @_;
+                my $find = $self->object->base->db->prepare_cached(q{
+                    SELECT name from employment where "user" = ?
+                        and firstday > ?
+                        order by firstday asc
+                });
+                $find->execute(
+                    $self->object->get_field('user'),
+                    $self->object->get_field('lastday')
+                );
+                my $res = $find->fetchrow_hashref;
+                $find->finish;
+                return $res->{name};
+            },
+            label => l('Next'),
+        },
+        minFirstDay => {
+            ro => 1,
+            hidden => 1,
+            managed => 1,
+            formtype => 'DATE',
+            get => sub {
+                my ($attr) = @_;
+                my $self = $attr->object;
+
+                my $find = $self->base->db->prepare_cached(q{
+                    SELECT max(lastday) as lastday FROM employment where "user" = ?
+                        and lastday is not NULL and lastday < ?
+                    });
+
+                my $first = $self->get_field('firstday');
+
+                $find->execute(
+                    $self->get_field('user'),
+                    $first
+                );
+                my $res = $find->fetchrow_hashref;
+                $find->finish;
+
+                if ($res && $res->{lastday}) {
+                    my $dt = DateTime->from_epoch( epoch => str2time($res->{lastday}));
+                    $dt->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+                    $dt->add(days => 1);
+                    if ($first eq $dt->ymd('-')) {
+                        return $first;
+                    } else {
+                        return $dt->ymd('-');
+                    }
+                } else {
+                    return;
+                }
+            },
+            label => l('Minimal first day'),
+        },
+        maxLastDay => {
+            ro => 1,
+            hidden => 1,
+            managed => 1,
+            formtype => 'DATE',
+            get => sub {
+                my ($attr) = @_;
+                my $self = $attr->object;
+
+                my $find = $self->base->db->prepare_cached(q{
+                    SELECT min(firstday) as firstday FROM employment where "user" = ?
+                        and firstday > ?
+                    });
+
+                my $last = $self->get_field('lastday') or do {
+                    # This contract is last then...
+                    return;
+                };
+
+                $find->execute(
+                    $self->get_field('user'),
+                    $last
+                );
+                my $res = $find->fetchrow_hashref;
+                $find->finish;
+
+                if ($res && $res->{firstday}) {
+                    my $dt = DateTime->from_epoch( epoch => str2time($res->{firstday}));
+                    $dt->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+                    $dt->subtract(days => 1);
+                    if ($last eq $dt->ymd('-')) {
+                        return $last;
+                    } else {
+                        return $dt->ymd('-');
+                    }
+                } else {
+                    return;
+                }
+            },
+            label => l('Maximal last day'),
+        },
+    };
+
+    if (! $base->config("allow_pasted_employment")) {
+    # Completed contract are RO, we allow to still set lastday
+        foreach (qw(endcircuit firstday contratType department managerContact company employer)) {
+            $attrs->{$_}{ro} = sub {
+                my ($self) = $_[0];
+                $self or return 0;
+                my $st = $self->get_attributes('state') || '0';
+                return $st < 0 ? 1 : 0;
+            };
+        }
+    }
+    $class->SUPER::_get_attr_schema($base, $attrs);
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+    $data{user} or return;
+    my $user = $base->get_object('user', $data{user});
+    $user or return;
+    my $res = $class->SUPER::_create($base, $id, %data);
+    $user->applyCurrentEmployment;
+    $res;
+}
+
+sub _delete {
+    my ($class, $base, $id) = @_;
+
+    my $obj = $base->get_object($class->type, $id)
+        or return;
+
+    my $user = $obj->_get_attributes('user');
+
+    my $res = $class->SUPER::_delete($base, $id);
+    if ($res) {
+        my $ouser = $base->get_object('user', $user);
+        $ouser->applyCurrentEmployment;
+    }
+
+    $res
+}
+
+sub _set_c_fields {
+    my ($self, %data) = @_;
+
+    my $res = $self->SUPER::_set_c_fields(%data);
+
+    if ($res) {
+        my $user = $self->base->get_object('user', $self->get_attributes('user')) or do {
+            $self->base->log(LA_ERR, "Cannot fetch user %s to apply employment", $self->get_attributes('user'));
+            return;
+        };
+
+        $user->applyCurrentEmployment;
+    }
+
+    return $res;
+}
+
+sub checkValues {
+    my ($class, $base, $obj, %changes) = @_;
+
+    my $user = $changes{user} || $obj->get_attributes('user');
+    my $id = ref $obj ? $obj->id : $obj;
+
+    my $firstday = exists($changes{firstday}) 
+        ? $changes{firstday} : 
+            ref $obj ? $obj->get_attributes('firstday') : undef;
+    my $lastday  = exists($changes{lastday})
+        ?  $changes{lastday} :
+            ref $obj ? $obj->get_attributes('lastday') : undef;
+
+    if ($lastday) {
+        my $sth = $base->db->prepare_cached(q{
+            select name, firstday, lastday from employment where "user" = ? and name != ?
+                and
+            (
+            (firstday <= ? and (lastday is NULL or lastday >= ?))
+                or
+            ((lastday is NOT NULL and lastday <= ?) and firstday >= ?)
+            )
+        });
+        $sth->execute($user, $id, $lastday, $firstday, $lastday, $firstday);
+        my $res = $sth->fetchrow_hashref;
+        $sth->finish;
+
+        if ($res) {
+            $base->log(LA_ERR, "The change will overlap contrat %s (%s - %s)", $res->{name}, $res->{firstday}, $res->{lastday} || '');
+            return;
+        }
+    } elsif (!$firstday) {
+        $base->log(LA_ERR, "No firstday given");
+        return;
+    } else {
+        my $sth = $base->db->prepare_cached(q{
+            select * from employment where "user" = ? and name != ?
+            and (lastday is NULL OR lastday >= ?)
+            limit 1
+        });
+        $sth->execute($user, $id, $firstday);
+        my $res = $sth->fetchrow_hashref;
+        if ($res && $res->{name}) {
+            if ($id ne $res->{name}) {
+                $base->log(LA_ERR, "Another contract has no ending (%s)", $res->{name} || '');
+                return;
+            }
+        }
+    }
+
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Group.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Group.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Group.pm	(revision 2119)
@@ -0,0 +1,321 @@
+package LATMOS::Accounts::Bases::Sql::Group;
+
+use 5.010000;
+use strict;
+use warnings;
+use overload '""' => 'stringify';
+
+use base qw(LATMOS::Accounts::Bases::Sql::objects);
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::I18N;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::Group - Groups objects support
+
+=cut
+
+sub stringify {
+    my ($self) = @_;
+
+    $self->get_field('label')
+    || $self->id;
+}
+
+sub _object_table { 'group' }
+
+sub _key_field { 'name' }
+
+sub _has_extended_attributes { 1 }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+
+    $class->SUPER::_get_attr_schema($base,
+        {
+            gidNumber  => {
+                inline => 1,
+                uniq => 1,
+                iname => 'gidnumber',
+                mandatory => 1,
+                label => l('GID'),
+            },
+            gidnumber  => { inline => 1, uniq => 1, hide => 1, },
+            cn         => { inline => 1, ro => 1, iname => 'name', },
+            memberUID  => {
+                monitored => 1,
+                hide => 1,
+                reference => 'user',
+                multiple => 1,
+                delayed => 1,
+                ro => sub {
+                    $_[0] &&
+                    (($_[0]->_get_c_field('sutype') ||'') =~ /^(jobtype|contrattype)$/
+                     || $_[0]->_get_c_field('autoMemberFilter')
+                     || $_[0]->_get_c_field('autoFromSutype'))
+                    ? 1 : 0
+                },
+                get => sub {
+                    my ($self) = @_;
+                    my $sth = $self->base->db->prepare_cached(
+                        q{
+                        select value from group_attributes
+                        join "group" on "group".ikey = group_attributes.okey
+                        join "user" on "user".name = group_attributes.value
+                        where "group".name = ? and attr = ?
+                        } .
+                        ($self->base->{wexported} ? '' : 'and "user".exported = true')
+                    );
+                    $sth->execute($self->object->id, 'memberUID');
+                    my @res;
+                    while (my $res = $sth->fetchrow_hashref) {
+                        push(@res, $res->{value});
+                    }
+                    return \@res;
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                    $self->object->_set_group_members($data);
+                },
+                label => l('Member'),
+            },
+            member     => {
+                monitored => 1,
+                reference => 'user',
+                multiple => 1,
+                delayed => 1,
+                iname => 'memberUID',
+                can_values => sub { $base->list_objects('user') },
+                ro => sub {
+                    $_[0] &&
+                    (($_[0]->_get_c_field('sutype') ||'') =~ /^(jobtype|contrattype)$/
+                     || $_[0]->_get_c_field('autoMemberFilter')
+                     || $_[0]->_get_c_field('autoFromSutype'))
+                    ? 1 : 0
+                },
+                get => sub {
+                    my ($self) = @_;
+                    $self->object->_get_c_field('memberUID');
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                    $self->object->_set_group_members($data);
+                },
+                label => l('Member'),
+            },
+            sAMAccountName => { iname => 'name', ro => 1 },
+            groupname  => { ro => 1 },
+            managedBy  => {
+                monitored => 1,
+                reference => 'user',
+                can_values => sub {
+                    my %uniq = map { $_ => 1 } grep { $_ }
+                    (($_[1] ? $_[1]->get_attributes('managedBy') : ()),
+                    $base->search_objects('user', 'active=*'));
+                    sort keys %uniq;
+                },
+                label => l('Manager'),
+                post => sub {
+                    my ($self, $value) = @_;
+                    $self->object->_update_employment_manager;
+                },
+            },
+            managedAlsoBy  => {
+                monitored => 1,
+                reference => 'user',
+                multiple => 1,
+                delayed => 1,
+                can_values => sub {
+                    my %uniq = map { $_ => 1 } grep { $_ }
+                    ($_[1] ? $_[1]->get_attributes('managedBy') : ()),
+                    $base->search_objects('user', 'active=*');
+                    sort keys %uniq;
+                },
+                label => l('Secondary Manager'),
+            },
+            sutype => {
+                reference => 'sutype',
+                monitored => 1,
+                label => l('Structure'),
+                multiple => 1,
+            },
+            autoMemberFilter => {
+                multiple => 1,
+                set => sub {
+                    my ($self, $data) = @_;
+                    $self->object->set_fields($self->name, $data) or return;
+                    $self->object->populate_dyn_group;
+                    return 1;
+                },
+                label => l('Automatics filters'),
+            },
+            autoFromSutype => {
+                reference => 'sutype',
+                multiple => 1,
+                set => sub {
+                    my ($self, $data) = @_;
+                    $self->object->set_fields($self->name, $data) or return;
+                    $self->object->populate_dyn_group;
+                    return 1;
+                },
+                label => l('From group type'),
+            },
+            label => {
+                label => l('Label'),
+            },
+            expire => {
+                label => l('Expire'),
+            },
+            description => {
+                label => l('Description'),
+            },
+            comment => {
+                label => l('Comment'),
+            },
+            memberCount => {
+                managed => 1,
+                ro => 1,
+                get => sub {
+                    my ($self) = @_;
+                    scalar(@{$self->object->_get_c_field('memberUID') || []});
+                },
+                label => l('Member count'),
+            },
+        }
+    )
+}
+
+sub _set_group_members {
+    my ($self, $members) = @_;
+    my %member;
+    my $res = 0;
+    foreach (@{ $self->_get_c_field('memberUID') || [] }) {
+        $member{$_}{c} = 1;
+    }
+    foreach (ref $members ? @{ $members || []} : $members) {
+        $_ or next; # avoid undef
+        $member{$_}{n} = 1;
+    }
+
+    foreach (keys %member) {
+        $member{$_}{c} && $member{$_}{n} and next; # no change !
+        my $user = $self->base->get_object('user', $_) or next;
+        if ($member{$_}{n}) {
+            my $sth = $self->db->prepare_cached(
+                q{insert into group_attributes_users (value, attr, okey) values (?,?,?)}
+            );
+            $res += $sth->execute($_, 'memberUID', $self->_get_ikey);
+        } elsif ($member{$_}{c}) {
+            if (($user->get_c_field('department') || '') eq $self->id) {
+                $self->base->log(LA_WARN,
+                    "Don't removing user %s from group %s: is it's department",
+                    $user->id, $self->id);
+                next;
+            }
+            my $sth = $self->db->prepare_cached(
+                q{delete from group_attributes_users where value = ? and attr = ? and okey = ?}
+            );
+            $res += $sth->execute($_, 'memberUID', $self->_get_ikey);
+        } # else {} # can't happend
+    }
+    return $res;
+}
+
+=head2 populate_dyn_group
+
+Synchronise group's members according filter set into C<autoMemberFilter> attribute.
+
+=cut
+
+sub populate_dyn_group {
+    my ($self) = @_;
+
+    if (
+           !$self->get_field('autoMemberFilter')
+        && !$self->get_field('autoFromSutype')) {
+        return 0;
+    }
+    $self->base->log(LA_DEBUG,
+        "Populating group %s from autoMemberFilter/autoFromSutype attribute",
+        $self->id
+    );
+
+    my %users;
+
+    if (my $filter = $self->get_field('autoFromSutype')) {
+        my @suTypeFilter = map { "sutype=$_" } (ref $filter ? @{ $filter } : $filter);
+        foreach my $group ($self->base->search_objects('group', @suTypeFilter)) {
+            foreach ($self->base->search_objects('user', "memberOf=$group")) {
+                $users{$_} = 1;
+            }
+        }
+    }
+
+    if (my $filter = $self->get_field('autoMemberFilter')) {
+        foreach ($self->base->search_objects(
+                'user',
+                'oalias=NULL',
+                ref $filter ? @{ $filter } : $filter)) {
+            $users{$_} = 1;
+        }
+    }
+
+    my $res = $self->_set_group_members([ keys %users ]);
+
+    $res;
+}
+
+sub _update_employment_manager {
+    my ($self) = @_;
+
+    my $listEmp = $self->base->db->prepare(q{
+        select name from employment join employment_attributes
+            on employment.ikey = employment_attributes.okey
+        where firstday < now() and (lastday > now() or lastday is NULL)
+            and employment_attributes.attr = 'department' and employment_attributes.value = ?
+        });
+    $listEmp->execute($self->id);
+    while (my $res = $listEmp->fetchrow_hashref()) {
+        $self->base->log(LA_DEBUG, "Checking if employment %s is impacted by managedBy change", $res->{name});
+        my $employment = $self->base->get_object('employment', $res->{name}) or next;
+        $employment->get_attributes('managerContact') and next;
+        my $dpmt  = $employment->_get_attributes('department') or next;
+        my $odmpt = $employment->base->get_object('group', $dpmt) or next;
+        my $manager = $odmpt->get_attributes('managedBy') or next;
+        my $user = $employment->get_attributes('user');
+        my $ouser = $employment->base->get_object('user', $user) or next;
+        $self->base->log(LA_DEBUG, "Updating manager to $manager for user $user due to dpmt update");
+        $ouser->ReportChange(
+            'Update', 'Attr manager updated to match Dpmt manager %s (%s)',
+            $manager,
+            $dpmt,
+        );
+        $ouser->set_fields('manager', $manager);
+    }
+
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Nethost.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Nethost.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Nethost.pm	(revision 2119)
@@ -0,0 +1,319 @@
+package LATMOS::Accounts::Bases::Sql::Nethost;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Sql::objects);
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::I18N;
+use Net::IP;
+
+our $VERSION = (q$Rev: 2104 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::Nethost - A Network Host entry
+
+=cut
+
+sub _object_table { 'nethost' }
+
+sub _key_field { 'name' }
+
+sub _has_extended_attributes { 1 }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+
+    $class->SUPER::_get_attr_schema($base,
+        {
+            name    => { ro => 1, inline => 1, },
+            cn      => { ro => 1, inline => 1, iname => 'name' },
+            date    => { ro => 1, inline => 1, },
+            create  => { ro => 1, inline => 1, },
+            expire    => {
+                inline => 1,
+                formtype => 'DATE',
+                label => l('Expire'),
+            },
+            endOfWarranty => {
+                formtype => 'DATE',
+                label => l('End of warranty'),
+            },
+            ip      => {
+                monitored => 1,
+                multiple => 1,
+                uniq => 1,
+                input => sub {
+                    my $ip = $_[0] or return;
+                    $ip =~ s/(\D|^)0+/$1/g;
+                    $ip =~ s:/.*$::;
+                    $ip
+                },
+                checkinputformat => sub {
+                    my ($value) = @_;
+                    $value or return;
+                    my $ip = Net::IP->new($value);
+                    $ip or return;
+                    $ip->prefixlen() == ($ip->version == 4 ? 32 : 128) or do {
+                        $base->log(LA_ERR, "Wrong prefix len for IP %s: %d (ip version %d)",
+                            $ip->print, $ip->prefixlen, $ip->version);
+                        return undef;
+                    };
+                },
+                label => l('Ip'),
+            },
+            ipFrom  => {
+                multiple => 1, 
+                managed => 1,
+                delayed => 1,
+                can_values => sub {
+                    $base->search_objects('netzone', 'type=dhcp')
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                    my $count = 0;
+                    foreach (ref $data ? @{$data} : $data) {
+                        my $zone = $self->base->get_object('netzone', $_)
+                            or next;
+                        my @freeips = $zone->get_attributes('freeIP') or next;
+                        my $idx = rand(scalar(@freeips));
+                        $self->object->_addAttributeValue('ip', $freeips[$idx]);
+                        $self->base->log(LA_NOTICE, "Assigning ip %s to host %s",
+                            $freeips[$idx], $self->object->id);
+                        $count++;
+                    }
+                    return $count;
+                },
+                label => l('Ip from...'),
+            },
+            macaddr => {
+                monitored => 1,
+                multiple => 1,
+                uniq => 1,
+                input => sub {
+                    $_[0] or return;
+                    $_[0] =~ /^([0-9a-f]{2}([:-]|$)){6}$/i or return $_[0];
+                    my @elem = split(/[:-]/, $_[0]);
+                    return join(':', map { sprintf("%02x", hex($_)) || $_ } @elem);
+                },
+                checkinputformat => sub {
+                    $_[0] or return;
+                    return $_[0] =~ /^([0-9a-f]{2}([:-]|$)){6}$/i ? 1 : undef;
+                },
+                label => l('Hardware address'),
+            },
+            cname   => {
+                monitored => 1,
+                multiple => 1,
+                input => sub { lc($_[0]) },
+                label => l('Aliases'),
+            },
+            related   => {
+                multiple => 1, uniq => 1,
+                reference => 'nethost',
+                label => l('Related'),
+            },
+            owner   => {
+                monitored => 1,
+                reference => 'user',
+                delayed => 1,
+                label => l('Owner'),
+            },
+            user    => {
+                reference => 'user',
+                delayed => 1,
+                label => l('User'),
+                monitored => 1,
+            },
+            netZone => {
+                multiple => 1, ro => 1, managed => 1,
+                reference => 'netzone',
+                get => sub {
+                    my ($self) = @_;
+                    my $find = $self->base->db->prepare_cached(q{
+                        select name from netzone where ikey in
+                        (
+                        select netzone_attributes.okey from netzone_attributes join
+                        nethost_attributes_ips on netzone_attributes.attr='net'
+                            and 
+                        nethost_attributes_ips.value::inet <<=
+                        netzone_attributes.value::inet
+                        where nethost_attributes_ips.okey = $1
+                        except
+                        select netzone_attributes.okey from netzone_attributes join
+                        nethost_attributes_ips on netzone_attributes.attr='netExclude'
+                            and nethost_attributes_ips.value::inet <<= netzone_attributes.value::inet
+                        where nethost_attributes_ips.okey = $1
+                        )
+                        order by name
+                        });
+                    $find->execute($self->object->get_attributes('ikey'));
+                    my @zones;
+                    while (my $res = $find->fetchrow_hashref) {
+                        push(@zones, $res->{name});
+                    }
+                    return @zones ? [ @zones ] : undef;
+                },
+                label => l('NetZones'),
+            },
+            puppetClass => {
+                multiple => 1,
+                label => l('Puppet Class'),
+            },
+            netZoneExclude => {
+                multiple => 1, ro => 1, managed => 1,
+                reference => 'netzone',
+                get => sub {
+                    my ($self) = @_;
+                    my $find = $self->base->db->prepare_cached(q{
+                        select name from netzone where ikey in
+                        (
+                        select netzone_attributes.okey from netzone_attributes join
+                        nethost_attributes_ips on netzone_attributes.attr='netExclude'
+                            and 
+                        nethost_attributes_ips.value::inet <<=
+                        netzone_attributes.value::inet
+                        where nethost_attributes_ips.okey = $1
+                        )
+                            order by name
+                        });
+                    $find->execute($self->object->get_attributes('ikey'));
+                    my @zones;
+                    while (my $res = $find->fetchrow_hashref) {
+                        push(@zones, $res->{name});
+                    }
+                    return @zones ? [ @zones ] : undef;
+                },
+            },
+            noDynamic => { 
+                formtype => 'CHECKBOX',
+                label => l('No dynamic IP'),
+            },
+            exported => { formtype => 'CHECKBOX', },
+            noInheritPuppet => {
+                formtype => 'CHECKBOX',
+                label => l('No puppet'),
+            },
+            otherName => {
+                multiple => 1,
+                input => sub { lc($_[0]) },
+                label => l('Other name'),
+            },
+            sshfp => {
+                multiple => 1,
+                label => l('SSH finger print'),
+            },
+            ip6 => { },
+            sshfpUpdate  => {
+                managed => 1,
+                iname => 'sshfp',
+                input => sub {
+                    my ($self, $data) = @_;
+                    $data or return;
+                    open(my $h, '<', $data) or return;
+                    my @sshfps;
+                    while (my $line = <$h>) {
+                        my ($type, $mode, $key) = $line =~ /^\S+\s+IN\s+SSHFP\s+(\d)\s+(\d)\s+(\S+)/i
+                            or do {
+                            la_log(LA_ERR, "Seems to not be a ssh-keygen line: %s", $line);
+                            return;
+                        };
+                        la_log(LA_DEBUG, "found sshfp %s %s %s", $type, $mode, $key);
+                        push(@sshfps, "$type $mode $key");
+                    }
+                    close($h);
+                    \@sshfps;
+                }
+            },
+            comment => {
+                label => l('Comment'),
+            },
+            description => {
+                label => l('Description'),
+            },
+            hostType => {
+                label => l('Host\'s type'),
+            },
+            encryptKey => {
+                label => l('Encrypted key'),
+            },
+            reverse => {
+                label => l('Reverse name'),
+            },
+            serialNumber => {
+                label => l('Serial number'),
+            },
+            inventoryNumber => {
+                label => l('Inventory number'),
+            },
+            site => {
+                reference => 'site',
+                label => l('Site'),
+            },
+            physicalDeliveryOfficeName => {
+                label => l('Office'),
+            },
+            netLocked => {
+                formtype => 'CHECKBOX',
+                monitored => 1,
+                label => l('Locked'),
+            },
+
+        }
+    )
+}
+
+sub set_fields {
+    my ($self, %data) = @_;
+
+    my $res = $self->SUPER::set_fields(%data);
+
+    # Post update related
+    if (exists $data{related}) {
+        my %exists;
+        if ($data{related}) {
+            my @related = ref $data{related} ? @{$data{related}} : $data{related};
+            foreach (@related) {
+                my $obj = $self->base->get_object('nethost', $_) or next;
+                $exists{$obj->id} = 1; 
+                my @current = grep { $_ } $obj->get_attributes('related');
+                grep { $_ eq $self->id } @current and next;
+                $obj->set_c_fields('related' => [ $self->id, @current ]);
+
+            }
+        }
+        foreach ($self->base->search_objects('nethost', 'related=' . $self->id)) {
+            my $obj = $self->base->get_object('nethost', $_) or next;
+            $exists{$_} and next;
+            my @current = grep { $_ eq $self->id } $obj->get_attributes('related');
+            $obj->set_c_fields('related' => [ @current ]);
+        }
+    }
+
+    $res
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql::Netzones>, L<LATMOS::Accounts::Bases::Sql>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Netzone.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Netzone.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Netzone.pm	(revision 2119)
@@ -0,0 +1,232 @@
+package LATMOS::Accounts::Bases::Sql::Netzone;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Sql::objects);
+use LATMOS::Accounts::I18N;
+
+our $VERSION = (q$Rev: 2104 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::Netzone - Network management
+
+=head1 DESCRIPTION
+
+The C<Netzone> object handle information for dns or dhcp management.
+
+=cut
+
+sub _object_table { 'netzone' }
+
+sub _key_field { 'name' }
+
+sub _has_extended_attributes { 1 }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+
+    $class->SUPER::_get_attr_schema($base,
+        {
+            name    => { ro => 1, inline => 1, },
+            cn      => { ro => 1, inline => 1, iname => 'name' },
+            date    => { ro => 1, inline => 1, },
+            create  => { ro => 1, inline => 1, },
+            net     => { multiple => 1, monitored => 1, },
+            netExclude => { multiple => 1, monitored => 1, },
+            group   => { hide => 1 },
+            allow_dyn => {
+                formtype => 'CHECKBOX',
+                ro => sub { 
+                    $_[0] && ($_[0]->get_attributes('type') || '') =~ /dhcp|radius/ ? 0 : 1
+                },
+            },
+            output => {},
+            outputD => {
+                hide => 1,
+                ro => 1,
+                managed => 1,
+                get => sub {
+                    $_[0]->object->_get_c_field('output') ||
+                    ($_[0]->object->id . (($_[0]->object->get_attributes('type') || '') eq 'puppet' ? '.pp' : ''))
+                },
+            },
+            template => {},
+            templateD => {
+                hide => 1,
+                ro => 1,
+                managed => 1,
+                get => sub {
+                    $_[0]->object->_get_c_field('template') ||
+                    $_[0]->object->id . '.in'
+                },
+            },
+            type    => {
+                mandatory => 1,
+                can_values => sub { qw(dhcp dns reverse puppet radius) },
+            },
+            site   => {
+                delayed => 1,
+                reference => 'site',
+            },
+            dynFrom   => {
+                delayed => 1,
+                multiple => 1,
+                reference => 'netzone',
+                ro => sub { 
+                    $_[0] && ($_[0]->get_attributes('type') || '') =~ /^(dhcp|radius)$/ ? 0 : 1
+                },
+            },
+            freeIP => {
+                managed => 1,
+                ro => 1,
+                multiple => 1,
+                get => sub {
+                    my $self = $_[0]->object;
+                    my @ips;
+                    my $findip = $self->base->db->prepare(q{
+                        select * from (
+                        select host(network(value::inet)+ (generate_series(1, broadcast(value::inet) - network(value::inet) -1))) from netzone_attributes where
+                        attr='net' and okey = $1 and family(value::inet) = 4
+                        except
+                        (
+                        select host(network(value::inet)+ (generate_series(0, broadcast(value::inet) - network(value::inet)))) from
+                        netzone_attributes where attr='netExclude' and okey = $1
+                        union
+                        select nethost_attributes_ips.value from nethost_attributes_ips join netzone_attributes on
+                        netzone_attributes.attr='net' and nethost_attributes_ips.value::inet <<= netzone_attributes.value::inet
+                        where netzone_attributes.okey = $1
+                        )
+                        ) as s
+                        order by host::inet
+                        });
+                    $findip->execute($self->get_attributes('ikey'));
+                    while (my $res = $findip->fetchrow_hashref) {
+                        push(@ips, $res->{host});
+                    }
+                    return scalar(@ips) ? \@ips : undef;
+                },
+            },
+            freeIPCount => {
+                managed => 1,
+                ro => 1,
+                get => sub {
+                    return scalar(grep { $_ } $_[0]->object->get_attributes('freeIP'));
+                },
+            },
+            ipCount => {
+                managed => 1,
+                ro => 1,
+                get => sub {
+                    my ($self) = $_[0]->object;
+                    my $findip = $self->base->db->prepare(q{
+                        select count(*) from (
+                        select host(network(value::inet)+ (generate_series(1, broadcast(value::inet) - network(value::inet) -1))) from netzone_attributes where
+                        attr='net' and okey = $1 and family(value::inet) = 4
+                        except
+                        (
+                        select host(network(value::inet)+ (generate_series(0, broadcast(value::inet) - network(value::inet)))) from
+                        netzone_attributes where attr='netExclude' and okey = $1
+                        )
+                        ) as s
+                        });
+                    $findip->execute($self->get_attributes('ikey'));
+                    my $res = $findip->fetchrow_hashref;
+                    $findip->finish;
+                    return $res->{count}
+                },
+            },
+            hosts =>  {
+                ro => 1,
+                multiple => 1,
+                managed => 1,
+                reference => 'nethost',
+                get => sub {
+                    my ($self) = $_[0]->object;
+                    my $findhost = $self->base->db->prepare_cached(q{
+                        select name from nethost join nethost_attributes_ips on
+                        nethost.ikey = nethost_attributes_ips.okey
+                        where value::inet <<= any(?)
+                        } . ($self->base->{wexported} ? '' : 'and exported = true') .
+                        q{ except
+                        select name from nethost join nethost_attributes_ips on
+                        nethost.ikey = nethost_attributes_ips.okey
+                        where value::inet <<= any(?)
+                        order by name
+                        }
+                    );
+                    $findhost->execute(
+                        [ $self->get_attributes('net') ],
+                        [ $self->get_attributes('netExclude') ],
+                    );
+                    my @hosts;
+                    while (my $res = $findhost->fetchrow_hashref) {
+                        push(@hosts, $res->{name});
+                    }
+                    return scalar(@hosts) ? \@hosts : undef;
+                },
+            },
+            hostsExclude =>  {
+                ro => 1,
+                multiple => 1,
+                managed => 1,
+                reference => 'nethost',
+                get => sub {
+                    my $self = $_[0]->object;
+                    my $findhost = $self->base->db->prepare_cached(q{
+                        select name from nethost join nethost_attributes_ips on
+                        nethost.ikey = nethost_attributes_ips.okey
+                        where value::inet <<= any(?)
+                        } . ($self->base->{wexported} ? '' : 'and exported = true') .
+                        q{
+                        order by name
+                        }
+                    );
+                    $findhost->execute(
+                        [ $self->get_attributes('netExclude') ],
+                    );
+                    my @hosts;
+                    while (my $res = $findhost->fetchrow_hashref) {
+                        push(@hosts, $res->{name});
+                    }
+                    return scalar(@hosts) ? \@hosts : undef;
+                },
+            },
+            lastBuild => { hide => 1, },
+            lastUpdate => { ro => 1, iname => 'lastBuild' },
+            zoneRevision => { hide => 1, },
+            dnsRevision =>  { ro => 1, iname => 'zoneRevision' },
+            exported => { formtype => 'CHECKBOX', },
+            puppetClass =>  { multiple => 1, },
+            domain => { },
+            description => { },
+            hostParams => { multiple => 1 }, 
+        }
+    )
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>
+L<LATMOS::Accounts::Bases::Sql::Nethost>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Onlyaddress.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Onlyaddress.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Onlyaddress.pm	(revision 2119)
@@ -0,0 +1,112 @@
+package LATMOS::Accounts::Bases::Sql::Onlyaddress;
+
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Sql::Address);
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::Onlyaddress - Secondary office Address support SQL base
+
+=head1 DESCRIPTION
+
+This module handle object for office address, allowing to support multiple
+address per people.
+
+=cut
+
+sub list {
+    my ($class, $base) = @_;
+
+    my $sth = $base->db->prepare_cached(
+        sprintf(
+            q{select %s.%s as k from %s
+              join "user" on %s."user" = "user".name %s
+              where %s.ikey not in (select okey from %s where attr = 'isMainAddress') %s
+              order by %s.%s
+            },
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_key_field),
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_object_table),
+            ($base->{wexported} ? '' : 'and "user".exported = true'),
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_object_table_attributes_read), # attr
+            ($base->{wexported} ? '' : 'and ' .
+                $base->db->quote_identifier($class->_object_table) . '.exported = true'),
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_key_field),
+        )
+    );
+    $sth->execute;
+    my @keys;
+    while(my $res = $sth->fetchrow_hashref) {
+        push(@keys, $res->{k});
+    }
+    @keys
+}
+
+sub list_from_rev {
+    my ($class, $base, $rev) = @_;
+
+    my $sth = $base->db->prepare_cached(
+        sprintf(
+            q{select %s as k from %s where ikey not in (select okey from %s
+            where attr = 'isMainAddress') and rev > ? %s order by %s},
+            $base->db->quote_identifier($class->_key_field),
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_object_table_attributes_read), # attr
+            ($base->{wexported} ? '' : 'and exported = true'),
+            $base->db->quote_identifier($class->_key_field),
+        )
+    );
+    $sth->execute($rev);
+    my @keys;
+    while(my $res = $sth->fetchrow_hashref) {
+        push(@keys, $res->{k});
+    }
+    @keys
+}
+
+sub new {
+    my ($class, $base, $id) = @_;
+    my $sth = $base->db->prepare_cached(
+        sprintf(q{select 1 from %s where %s = ? %s and ikey not in (select okey from %s where attr = 'isMainAddress')},
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_key_field),
+            ($base->{wexported} ? '' : 'and exported = true'),
+            $base->db->quote_identifier($class->_object_table_attributes_read),
+        ),
+    );
+    my $count = $sth->execute($id);
+    $sth->finish;
+    $count == 1 or return;
+    $class->SUPER::new($base, $id);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>
+
+L<LATMOS::Accounts::Bases::Sql::Address>
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009, 2010, 2011, 2012 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Revaliases.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Revaliases.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Revaliases.pm	(revision 2119)
@@ -0,0 +1,90 @@
+package LATMOS::Accounts::Bases::Sql::Revaliases;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Sql::objects);
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::I18N;
+
+our $VERSION = (q$Rev: 341 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ad - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Accounts::Bases;
+  my $base = LATMOS::Accounts::Bases->new('sql');
+  ...
+
+=head1 DESCRIPTION
+
+Account base access over standard unix file format.
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 new(%config)
+
+Create a new LATMOS::Ad object for windows AD $domain.
+
+domain / server: either the Ad domain or directly the server
+
+ldap_args is an optionnal list of arguments to pass to L<Net::LDAP>.
+
+=cut
+
+sub _object_table { 'revaliases' }
+
+sub _key_field { 'name' }
+
+sub _has_extended_attributes { 0 }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+
+    $class->SUPER::_get_attr_schema($base,
+        {
+            name            => { reference => 'user', inline => 1, },
+        }
+    )
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+
+    my $uobj = $base->get_object('user', $id) or do {
+        $base->log(LA_ERR, 'Cannot create revaliases %s: no such user', $id);
+        return;
+    };
+    $data{exported} = $uobj->get_attributes('exported') || 0;
+
+    $class->SUPER::_create($base, $id, %data);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql::Aliases>, L<LATMOS::Accounts::Bases::Sql> 
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Service.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Service.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Service.pm	(revision 2119)
@@ -0,0 +1,100 @@
+package LATMOS::Accounts::Bases::Sql::Service;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Sql::objects);
+use LATMOS::Accounts::I18N;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::Site - Common Location/Address object
+
+=head1 DESCRIPTION
+
+Store common to many people office address (typically building location).
+
+=cut
+
+sub _object_table { 'service' }
+
+sub _key_field { 'name' }
+
+sub _has_extended_attributes { 1 }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+
+    $class->SUPER::_get_attr_schema($base,
+        {
+            name   => { ro => 1, inline => 1, },
+            date   => { ro => 1, inline => 1, },
+            create => { ro => 1, inline => 1, },
+            start  => {
+                inline => 1, formtype => 'DATE',
+                label => l('Start'),
+            },
+            end    => {
+                inline => 1, formtype => 'DATE',
+                label => l('End'),
+            },
+            type   => { },
+
+            dependOn => {
+                multiple => 1,
+                can_values => sub {
+                    my $sth = $base->db->prepare_cached(
+                        q{
+                        select relname, name from "objects_table"
+                        where exported = true
+                        }
+                    );
+                    $sth->execute();
+                    my @res;
+                    while (my $res = $sth->fetchrow_hashref) {
+                        push(@res, $res->{relname} . '.' . $res->{name});
+                    }
+                    return @res;
+                },
+                label => l('Depend on'),
+            },
+            manager => {
+                multiple => 1,
+                reference => 'user',
+                label => l('Manager'),
+            },
+            comment => {
+                label => l('Comment'),
+            },
+            description => {
+                label => l('Description'),
+            },
+        }
+    )
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Site.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Site.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Site.pm	(revision 2119)
@@ -0,0 +1,92 @@
+package LATMOS::Accounts::Bases::Sql::Site;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Sql::objects);
+use LATMOS::Accounts::I18N;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::Site - Common Location/Address object
+
+=head1 DESCRIPTION
+
+Store common to many people office address (typically building location).
+
+=cut
+
+sub _object_table { 'site' }
+
+sub _key_field { 'name' }
+
+sub _has_extended_attributes { 1 }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+
+    $class->SUPER::_get_attr_schema($base,
+        {
+            name   => { ro => 1, inline => 1, },
+            cn     => { ro => 1, inline => 1, iname => 'name' },
+            date   => { ro => 1, inline => 1, },
+            create => { ro => 1, inline => 1, },
+            streetAddress => { formtype => 'TEXTAREA' },
+            postalAddress => {
+                ro => 1,
+                get => sub {
+                    my $self = $_[0]->object;
+                    my $bp = $self->_get_c_field('postOfficeBox');
+                    return join("\n", grep { $_ } (
+                            $self->_get_c_field('o'),
+                            $self->_get_c_field('streetAddress'),
+                            ($bp ? 'BP ' . $bp : ''),
+                            join(' ', grep { $_ } (
+                                    $self->_get_c_field('postalCode'),
+                                    $self->_get_c_field('l'),)),
+                            join(' ', grep { $_ } (
+                                    $self->_get_c_field('st'),
+                                    $self->_get_c_field('co'),),
+                            )
+                        )
+                    );
+                },
+            },
+            siteNick => { uniq => 1 },
+            st => { },
+            postalCode => { },
+            postOfficeBox => { },
+            o => { },
+            l => { },
+            facsimileTelephoneNumber => { },
+            description => { },
+            co => { },
+        }
+    )
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Stat.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Stat.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Stat.pm	(revision 2119)
@@ -0,0 +1,316 @@
+package LATMOS::Accounts::Bases::Sql::Stat;
+
+use 5.010000;
+use strict;
+use warnings;
+use LATMOS::Accounts::Log;
+
+use base qw(LATMOS::Accounts::Bases::Sql::objects);
+use LATMOS::Accounts::I18N;
+
+use DateTime;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::Site - Common Location/Address object
+
+=head1 DESCRIPTION
+
+Store common to many people office address (typically building location).
+
+=cut
+
+sub _object_table { 'stat' }
+
+sub _key_field { 'name' }
+
+sub _has_extended_attributes { 1 }
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+
+    $class->SUPER::_get_attr_schema($base,
+        {
+            cn     => { ro => 1, inline => 1, iname => 'name' },
+            description => { },
+            otype  => { },
+            filter => { multiple => 1 },
+            attribute => { },
+            refFilter => { multiple => 1 },
+            refall => { },
+            nullValue => { },
+            aggregateFunction => {
+                can_values => sub { qw(sum avg count) },
+            },
+            display => { },
+            delay => { },
+            retention => { },
+            lastStatId => { },
+            needRun => {
+                ro => 1,
+                managed => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my ($self) = $attr->object;
+
+                    my $days = $self->_get_attributes('delay') || 7;
+                    my $sth = $self->db->prepare(
+                        q{
+                        select tstamp from statsentry where okey = ?
+                            and tstamp > now() - (? * '1 day'::interval)
+                            order by tstamp desc
+                            limit 1
+                        }
+                    );
+                    $sth->execute($self->Iid, $days);
+                    if (my $res = $sth->fetchrow_hashref) {
+                        $self->base->log(LA_DEBUG, "Stat %s done recently: %s", $self->id, $res->{tstamp});
+                        return undef;
+                    } else {
+                        return 1;
+                    }
+                },
+            },
+        }
+    )
+}
+
+=head2 compute()
+
+Compute stat value and return hash with var/val peer value
+
+=cut
+
+sub compute {
+    my ($self) = @_;
+
+    my $otype = $self->_get_attributes('otype') or return;
+    my $attribute = $self->_get_attributes('attribute') or return;
+
+    my %stats;
+    my $reffiltered = undef;
+
+    my $op = $self->_get_attributes('aggregateFunction');
+
+    my ($refOtype, $refAttr) = ($otype, undef);
+
+    foreach (split(/\./, $attribute)) {
+        if (my $attr = $self->base->attribute($refOtype, $_)) {
+            if (my $obj = $attr->reference) {
+                ($refOtype, $refAttr) = ($obj, $attr);
+            } else {
+                ($refOtype, $refAttr) = (undef, undef);
+            }
+        }
+    }
+
+    if ($refAttr && $refOtype) {
+        if (my @reffilter = $self->_get_attributes('refFilter')) {
+            $reffiltered = {};
+            foreach ($self->base->search_objects($refOtype, @reffilter)) {
+                $reffiltered->{$_} = 1;
+            }
+        }
+        if ($self->_get_attributes('refall') && $refAttr->has_values_list && !$op) {
+            foreach ($refAttr->can_values) {
+                if ($reffiltered && !$reffiltered->{$_}) {
+                    next;
+                }
+                $stats{$_} = 0;
+            } 
+        }
+    } else {
+        $self->base->log(LA_ERR, "Cannot find attribute %s for object type %s", $attribute, $otype);
+    }
+
+    my %filtered;
+    if (my @fil = $self->_get_attributes('filter')) {
+        %filtered = map { $_ => 1 } $self->base->search_objects($otype, @fil, 'oalias=NULL');
+    } else {
+        %filtered = map { $_ => 1 } $self->base->list_objects($otype);
+    }
+
+    my %results = $self->base->attributes_summary_by_object($otype, $attribute);
+
+    my $nullValue = $self->_get_attributes('nullValue');
+    my %aggdata;
+    if ($nullValue) {
+        $results{ $_ } ||= [''] foreach(keys %filtered);
+    }
+    foreach my $id (keys %results) {
+        $filtered{ $id } or next;
+        foreach (@{ $results{ $id }}) {
+            next unless (defined($_) || $nullValue);
+            if ($_ && $reffiltered && !$reffiltered->{$_}) {
+                next;
+            }
+            defined($_) && $_ ne '' or $_ = '(none)';
+            if ($op) {
+                push(@{$aggdata{$id}}, $_);
+            } else {
+                $stats{ $_ } ||= 0;
+                $stats{ $_ }++;
+            }
+        }
+    }
+    if ($op) {
+        foreach my $key (keys %aggdata) {
+            if ($op eq 'sum') {
+                my $sum = 0;
+                $sum += $_ foreach (@{ $aggdata{$key} });
+                $stats{ $key } = $sum;
+            }
+            elsif ($op eq 'count') {
+                $stats{ $key } = scalar(@{ $aggdata{$key} });
+            }
+            elsif ($op eq 'average') {
+                my $sum = 0;
+                $sum += $_ foreach (@{ $aggdata{$key} });
+                $stats{ $key } = $sum / scalar(@{ $aggdata{$key} });
+            }
+        }
+    }
+
+    return \%stats;
+}
+
+=head2 collect()
+
+Store statistics for $attribute on $otype. @filters allow to limit result on
+$otype object matching the filters
+
+=cut
+
+sub collect {
+    my ($self) = @_;
+
+    if (!$self->_get_attributes('needRun')) {
+        return;
+    }
+
+    if (my $retention = $self->_get_attributes('retention')) {
+        my $limit = DateTime->now->subtract(days => $retention);
+
+        my $clean = $self->db->prepare(
+            q{ DELETE FROM statsentry where tstamp < ? and okey = ? }
+        );
+
+        $clean->execute($limit->iso8601, $self->Iid);
+    }
+
+    my $otype = $self->_get_attributes('otype') or return;
+    my $attribute = $self->_get_attributes('attribute') or return;
+
+    $self->base->log(LA_NOTICE, "Collecting data for stat: %s", $self->id);
+
+    my $stats = $self->compute();
+
+    my $sthr = $self->db->prepare(
+        q{ INSERT INTO statsentry (okey) values (?) }
+    );
+    $sthr->execute($self->Iid);
+
+    my $id = $self->db->last_insert_id(undef,undef,'statsentry',undef);
+
+    my $sthv =  $self->db->prepare(
+        q{
+        INSERT INTO statvalues (sid, value, count) values (?,?,?)
+        }
+    );
+    foreach (keys %$stats) {
+        $sthv->execute($id, $_, $stats->{$_});
+    }
+    $self->_set_c_fields('lastStatId' => $id) or do {
+        $self->base->log(LA_ERR, "Cannot update Stat object");
+        return;
+    };
+
+    return 1;
+}
+
+=head2 getStat($id)
+
+Return the statistics value for this stat entry.
+
+If not given, C<$id> will be the last entry found
+
+=cut
+
+sub getStat {
+    my ($self, $id) = @_;
+
+    ($id) ||= $self->_get_attributes('lastStatId');
+
+    if (!$id) {
+        $self->base->log(LA_ERR, "No Stat Id given for stat name %s", $self->id);
+        return;
+    }
+
+    my $sthentry = $self->db->prepare('SELECT * from statsentry where okey = ? and id = ?');
+    $sthentry->execute($self->Iid, $id);
+    if (!(my $res = $sthentry->fetchrow_hashref)) {
+        $self->base->log(LA_ERR, "No stat entry %d found for stat %s", $id, $self->id);
+        return;
+    }
+
+    my $sthvalues = $self->db->prepare("SELECT * FROM statvalues where sid = ?");
+    $sthvalues->execute($id);
+
+    my $stats = {};
+    while (my $res = $sthvalues->fetchrow_hashref) {
+        $stats->{$res->{value}} = $res->{count};
+    }
+
+    $stats
+}
+
+=head2 getAllStat()
+
+Return a hashref containing all statistics value per date
+
+=cut
+
+sub getAllStat {
+    my ($self) = @_;
+
+    my $sth = $self->db->prepare(q{
+        SELECT count, value, to_char(tstamp, 'YYYY-MM-DD HH24:MI') as d from statsentry join
+                      statvalues
+            on statsentry.id = statvalues.sid
+            where statsentry.okey = ?
+                order by statsentry.tstamp, value
+    });
+    $sth->execute($self->Iid);
+
+    my $stats = {};
+    while(my $res = $sth->fetchrow_hashref) {
+        $stats->{$res->{d}}{$res->{value}} = $res->{count};
+    }
+
+    return $stats;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Sutype.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Sutype.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Sutype.pm	(revision 2119)
@@ -0,0 +1,49 @@
+package LATMOS::Accounts::Bases::Sql::Sutype;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Sql::objects);
+use LATMOS::Accounts::I18N;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::Sutype - Structural type
+
+=head1 DESCRIPTION
+
+Type for groups.
+
+=cut
+
+sub _object_table { 'su_type' }
+
+sub _key_field { 'name' }
+
+sub _has_extended_attributes { 0 }
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/User.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/User.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/User.pm	(revision 2119)
@@ -0,0 +1,2185 @@
+package LATMOS::Accounts::Bases::Sql::User;
+
+use 5.010000;
+use strict;
+use warnings;
+use overload '""' => 'stringify';
+
+use LATMOS::Accounts::Utils;
+use LATMOS::Accounts::Log;
+use POSIX qw(strftime);
+use Date::Parse qw(str2time);
+use DateTime;
+use DateTime::TimeZone;
+use base qw(LATMOS::Accounts::Bases::Sql::objects);
+use LATMOS::Accounts::I18N;
+use Date::Calc;
+use LATMOS::Accounts::Mail;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ad - Perl extension for blah blah blah
+
+=head1 DESCRIPTION
+
+Account base access over standard unix file format.
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 new(%config)
+
+Create a new LATMOS::Ad object for windows AD $domain.
+
+domain / server: either the Ad domain or directly the server
+
+ldap_args is an optionnal list of arguments to pass to L<Net::LDAP>.
+
+=cut
+
+sub _object_table { 'user' }
+
+sub _key_field { 'name' }
+
+sub _has_extended_attributes { 1 }
+
+sub stringify {
+    my ($self) = @_;
+
+    return join(' ', grep { $_ }
+        (
+            $self->get_field('givenName'),
+            $self->get_field('sn')
+        )
+    )
+    || $self->get_field('description')
+    || $self->id;
+}
+
+sub GetOtypeDef {
+    my ($class) = @_;
+
+    {
+        address => 'user',
+        employment => 'user',
+    },
+}
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+
+    my $subsetaddress = sub {
+        my ($self, $data) = @_;
+        my $fmainaddress = $self->object->_get_c_field('mainaddress');
+
+        # set address attribute => create address object on the fly
+        # except if attr is empty !
+        if (!$fmainaddress && $data) {
+            $fmainaddress = $self->object->id . '-' . join('', map { ('a'..'z')[rand(26)] }
+                (0..4));
+            $self->base->_create_c_object(
+                'address', $fmainaddress,
+                user => $self->object->id,
+                isMainAddress => 1, ) or do {
+                $self->base->log(LA_ERR,
+                    "Cannot create main address for user %s", $self->object->id);
+                return;
+            };
+        }
+        if ($fmainaddress && 
+            (my $address = $self->base->get_object('address', $fmainaddress))) {
+            if ($address->attribute($self->name) &&
+                !$address->attribute($self->name)->ro) {
+                return $address->set_c_fields($self->name => $data) ||0;
+            }
+        }
+    };
+
+    my $attrs = {
+            exported => {
+                post => sub {
+                    my ($self, $value) = @_;
+                    my $attr = $self->name;
+
+                    if (my $obj = $self->base->
+                            get_object('revaliases', $self->object->id)) {
+                        my $ares = $obj->set_c_fields(
+                            ($attr eq 'exported' ? 'exported' : 'unexported') => $value
+                        );
+                        if (!defined($ares)) {
+                            $self->base->log(LA_ERR,
+                                'Cannot set revaliases exported attribute for user %s',
+                                $self->object->id);
+                        }
+                    }
+                    my $must_expire = $self->name eq 'exported'
+                        ? ($value ? 0 : 1 )
+                        : ($value ? 1 : 0 );
+
+                    foreach my $al (grep { $_ } $self->object->get_attributes('aliases'), $self->object->id) {
+                        my $obj = $self->base->get_object('aliases', $al) or next;
+                        $obj->_set_c_fields(
+                            exported => $value,
+                        );
+                    }
+                    foreach my $al ($self->object->get_attributes('otheraddress')) {
+                        my $obj = $self->base->get_object('address', $al) or next;
+                        $obj->_set_c_fields(
+                            exported => $value,
+                        );
+                    }
+                },
+            },
+            uidNumber => {
+                inline => 1,
+                iname => 'uidnumber',
+                uniq => 1,
+                mandatory => 1,
+                formopts => { length => 7 },
+                label => l('UID'),
+            },
+            uidnumber => { inline => 1, hide => 1, monitored => 1 },
+            gidNumber => {
+                inline => 1,
+                iname => 'gidnumber',
+                mandatory => 1,
+                can_values => sub {
+                    map { $_->id, $_->get_attributes('gidNumber') }
+                    map { $base->get_object('group', $_) }
+                    $base->list_objects('group')
+                },
+                can_values => sub {
+                    my $sth = $base->db->prepare_cached(
+                        q{
+                        select name, gidnumber from "group" where exported = true
+                        }
+                    );
+                    $sth->execute();
+                    my @res;
+                    while (my $res = $sth->fetchrow_hashref) {
+                        push(@res, $res->{name});
+                        push(@res, $res->{gidnumber});
+                    }
+                    return @res;
+                },
+                display => sub {
+                    my ($self, $val) = @_;
+                    if ($val =~ /^\d+$/) {
+                        my ($gr) = $self->base->search_objects('group', "gidNumber=$val")
+                            or return;
+                        return $gr;
+                    } else {
+                        my ($gr) = $self->base->search_objects('group', "name=$val")
+                            or return;
+                        return $gr;
+                    }
+                },
+                input => sub {
+                    my ($val) = @_;
+                    $val =~ /^\d+$/ and return $val;
+                    my ($gr) = $base->search_objects('group', "name=$val") or return;
+                    return $base->get_object('group', $gr)->get_attributes('gidNumber');
+                },
+                #reference => 'group',
+                label => l('GID'),
+            },
+            loginShell => {
+                mandatory => 1,
+                label => l('Shell'),
+            },
+            gidnumber => { inline => 1, hide => 1,
+                can_values => sub {
+                    map { $_->id, $_->get_attributes('gidNumber') }
+                    map { $base->get_object('group', $_) }
+                    $base->list_objects('group')
+                },
+                display => sub {
+                    my ($self, $val) = @_;
+                    my ($gr) = $self->base->search_objects('group', "gidNumber=$val")
+                        or return;
+                    return $gr;
+                },
+                input => sub {
+                    my ($val) = @_;
+                    $val =~ /^\d+$/ and return $val;
+                    my ($gr) = $base->search_objects('group', "name=$val") or return;
+                    return $base->get_object('group', $gr)->get_attributes('gidNumber');
+                },
+                mandatory => 1,
+                reference => 'group',
+                monitored => 1,
+            },
+            locked    => {
+                formtype => 'CHECKBOX',
+                formopts => { rawvalue => 1, },
+                monitored => 1,
+                label => l('Locked'),
+            },
+            expire        => {
+                inline => 1,
+                formtype => 'DATETIME',
+                monitored => 1,
+                label => l('Expire on'),
+            },
+            endcircuit    => {
+                inline => 1,
+                formtype => 'DATE',
+                monitored => 1,
+                label => l('End of entrance'),
+            },
+            _endEmployment => {
+                formtype => 'DATETIME',
+                managed => 1,
+                ro => 1,
+                hide => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+                    $self->_computeEndEmployment($self->base->config('employment_delay') || 0);
+                },
+                label => l('End of employment'),
+            },
+            endEmployment => {
+                formtype => 'DATETIME',
+                ro => 1,
+                label => l('End of employment'),
+            },
+            _departure => {
+                formtype => 'DATETIME',
+                managed => 1,
+                ro => 1,
+                hide => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+                    $self->_computeEndEmployment($self->base->config('employment_delay') || 0, 1, 1);
+                },
+                label => l('Start of employment'),
+            },
+            departure => {
+                formtype => 'DATETIME',
+                ro => 1,
+                label => l('Leaving'),
+            },
+            _endStrictEmployment => {
+                formtype => 'DATETIME',
+                managed => 1,
+                ro => 1,
+                hide => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+                    $self->_computeEndEmployment();
+                },
+            },
+            endStrictEmployment => {
+                formtype => 'DATETIME',
+                ro => 1,
+            },
+            _endCurrentEmployment => {
+                formtype => 'DATETIME',
+                managed => 1,
+                ro => 1,
+                hide => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+
+                    my $list_empl = $self->base->db->prepare_cached(q{
+                        SELECT * FROM employment WHERE "user" = ? and
+                            firstday <= now() and
+                            (lastday is null or lastday >= now() - '1 days'::interval) 
+                            order by firstday asc
+                    });
+                    $list_empl->execute($self->id);
+                    my $end;
+                    while (my $res = $list_empl->fetchrow_hashref) {
+                        if (!$res->{lastday}) {
+                            # Ultimate employment.
+                            $list_empl->finish;
+                            return undef;
+                        } else {
+                            $end = DateTime->from_epoch(epoch => str2time($res->{lastday}));
+                            $end->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+                            $end->add(hours => 23, minutes => 59, seconds => 59);
+                        }
+                        last;
+                    }
+                    $list_empl->finish;
+
+                    return $end ? $end->iso8601 : undef
+                },
+            },
+            endCurrentEmployment => {
+                formtype => 'DATETIME',
+                ro => 1,
+            },
+            _endLastEmployment => {
+                formtype => 'DATETIME',
+                managed => 1,
+                ro => 1,
+                hide => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+
+                    my $list_empl = $self->base->db->prepare_cached(q{
+                        SELECT * FROM employment WHERE "user" = ?
+                        order by lastday desc nulls first
+                    });
+                    $list_empl->execute($self->id);
+                    my $res = $list_empl->fetchrow_hashref;
+                    $list_empl->finish;
+                    my $end;
+                    if ($res && $res->{lastday}) {
+                        $end = DateTime->from_epoch(epoch => str2time($res->{lastday}));
+                        $end->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+                        $end->add(hours => 23, minutes => 59, seconds => 59);
+                    }
+                    return $end ? $end->iso8601 : undef
+                },
+                label => l('End of any employment'),
+            },
+            endLastEmployment => {
+                formtype => 'DATETIME',
+                ro => 1,
+                label => l('End of any employment'),
+            },
+            _startEmployment => {
+                formtype => 'DATETIME',
+                managed => 1,
+                ro => 1,
+                hide => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+                    $self->_computeStartEmployment($self->base->config('employment_delay') || 0);
+                },
+                label => l('Start of employment'),
+            },
+            startEmployment => {
+                formtype => 'DATETIME',
+                ro => 1,
+                label => l('Start of employment'),
+            },
+            _arrival => {
+                formtype => 'DATETIME',
+                managed => 1,
+                ro => 1,
+                hide => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+                    $self->_computeStartEmployment($self->base->config('employment_delay') || 0, 1, 1);
+                },
+                label => l('Start of employment'),
+            },
+            arrival => {
+                formtype => 'DATETIME',
+                ro => 1,
+                label => l('Arrival'),
+            },            
+            _startStrictEmployment => {
+                formtype => 'DATETIME',
+                managed => 1,
+                ro => 1,
+                hide => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+                    $self->_computeStartEmployment();
+                },
+            },
+            startStrictEmployment => {
+                formtype => 'DATETIME',
+                ro => 1,
+            },
+            _startCurrentEmployment => {
+                formtype => 'DATETIME',
+                managed => 1,
+                ro => 1,
+                hide => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+
+                    my $list_empl = $self->base->db->prepare_cached(q{
+                        SELECT * FROM employment WHERE "user" = ? and
+                            firstday <= now() and
+                            (lastday is null or lastday >= now() - '1 days'::interval) 
+                            order by firstday asc
+                    });
+                    $list_empl->execute($self->id);
+                    my $start;
+                    while (my $res = $list_empl->fetchrow_hashref) {
+                        $start = DateTime->from_epoch(epoch => str2time($res->{firstday}));
+                        $start->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+                        last;
+                    }
+                    $list_empl->finish;
+
+                    return $start ? $start->iso8601 : undef
+                },
+            },
+            startCurrentEmployment => {
+                formtype => 'DATETIME',
+                ro => 1,
+            },
+            _startFirstEmployment => {
+                formtype => 'DATETIME',
+                managed => 1,
+                ro => 1,
+                hide => 1,
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+
+                    my $list_empl = $self->base->db->prepare_cached(q{
+                        SELECT * FROM employment WHERE "user" = ?
+                        order by firstday asc
+                    });
+                    $list_empl->execute($self->id);
+                    my $res = $list_empl->fetchrow_hashref;
+                    $list_empl->finish;
+                    my $start;
+                    if ($res && $res->{firstday}) {
+                        $start = DateTime->from_epoch(epoch => str2time($res->{firstday}));
+                        $start->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+                    }
+                    return $start ? $start->iso8601 : undef
+                },
+                label => l('Start of any employment'),
+            },
+            startFirstEmployment => {
+                formtype => 'DATETIME',
+                ro => 1,
+                label => l('Start of any employment'),
+            },
+            employmentLength => {
+                ro => 1,
+                managed => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $lastday = $self->object->get_attributes('endEmployment') || DateTime->now->ymd('-');
+                    my $firstday = $self->object->get_attributes('startEmployment') or return;
+
+                    my @fd = split('-', DateTime->from_epoch(epoch => str2time($firstday))->ymd('-'));
+                    my @ld = split('-', DateTime->from_epoch(epoch => str2time($lastday))->ymd('-'));
+
+                    return Date::Calc::Delta_Days(@fd, @ld) +1;
+                },
+                label => l('Work duration'),
+            },
+            employmentLengthText => {
+                ro => 1,
+                managed => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $firstday = $self->object->get_attributes('startEmployment') or return;
+                    my $lastday = $self->object->get_attributes('endEmployment')|| DateTime->now->ymd('-');
+                    {
+                        my $dtlast = DateTime->from_epoch(epoch => str2time($lastday));
+                        $dtlast->add(days => 1);
+                        $lastday = $dtlast->ymd('-');
+                    }
+
+                    my @fd = split('-', DateTime->from_epoch(epoch => str2time($firstday))->ymd('-'));
+                    my @ld = split('-', DateTime->from_epoch(epoch => str2time($lastday))->ymd('-'));
+
+                    my ($Dy,$Dm,$Dd) = Date::Calc::N_Delta_YMD(@fd, @ld);
+                    return join(', ',
+                        ($Dy ? l('%d years', $Dy)  : ()),
+                        ($Dm ? l('%d months', $Dm) : ()),
+                        ($Dd ? l('%d days', $Dd)   : ()),
+                    );
+                },
+                label => l('Work duration'),
+            },
+            cn        => {
+                inline => 1, ro => 1,
+                get => sub {
+                    my ($self) = @_;
+                    return join(' ', grep { $_ } 
+                        (
+                            $self->object->_get_c_field('givenName'),
+                            $self->object->_get_c_field('sn')
+                        )
+                    )
+                    || $self->object->_get_c_field('description')
+                    || $self->object->id;
+                },
+            },
+            memberOf  => {
+                reference => 'group',
+                multiple => 1, delayed => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $obj = $self->object;
+                    my $sth = $obj->db->prepare_cached(
+                        q{
+                        select name from "group" join
+                        group_attributes on group_attributes.okey = "group".ikey
+                        where value = ? and attr = ? and internobject = false
+                        } . ($self->base->{wexported} ? '' : ' and "group".exported = true')
+                    );
+                    $sth->execute($obj->id, 'memberUID');
+                    my @res;
+                    while (my $res = $sth->fetchrow_hashref) {
+                        push(@res, $res->{name});
+                    }
+                    return \@res;
+                },
+                set => sub {
+                    my ($self, $values) = @_;
+                    my %old = map { $_ => 'o' } @{ $self->get };
+                    foreach my $group (grep { $_ } ref $values ? @{ $values } : $values) {
+                        if ($old{$group}) {
+                            $old{$group} = undef; # no change
+                        } else {
+                            $old{$group} = 'n';
+                        }
+                    }
+
+                    my $res = 0;
+                    foreach my $group (keys %old) {
+                        $old{$group} or next; # no change
+
+                        my $ogroup = $self->base->get_object('group', $group) or next;
+                        ($ogroup->_get_c_field('sutype') || '') =~ /^(jobtype|contrattype)$/ and next;
+
+                        if ($old{$group} eq 'n') {
+                            $res += $ogroup->_addAttributeValue('memberUID', $self->object->id);
+                        } else {
+                            if (($self->object->_get_c_field('department') || '') eq $ogroup->id) {
+                                $self->base->log(LA_WARN,
+                                    "Don't removing user %s from group %s: is its department",
+                                    $self->object->id, $ogroup->id);
+                                next;
+                            }
+                            $res += $ogroup->_delAttributeValue('memberUID', $self->object->id);
+                        }
+                    }
+                    return $res;
+                },
+                label => l('Member of'),
+            },
+            forward => {
+                managed => 1,
+                multiple => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $sth = $self->base->db->prepare(q{
+                        select forward from aliases where name = ? and internobject = false
+                        } . ($self->base->{wexported} ? '' : ' and exported = true'));
+                    $sth->execute($self->object->id);
+                    my $res = $sth->fetchrow_hashref;
+                    $sth->finish;
+                    return $res->{forward}
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                    if ($data) {
+                        my @datas = ref $data ? @$data : split(/\s*,\s*/, $data);
+                        if (my $f = $self->base->get_object('aliases', $self->object->id)) {
+                            return $f->_set_c_fields(
+                                forward => \@datas,
+                                comment => undef,
+                                description => 'Forward for user ' . $self->object->id,
+                            );
+                        } else {
+                            if ($self->base->_create_c_object(
+                                    'aliases', $self->object->id,
+                                    forward => \@datas,
+                                    description => 'automatically created for ' . $self->object->id,
+                                )) {
+                                return 1;
+                            } else {
+                                $self->base->log(LA_ERR, "Cannot add forward for %s",
+                                    $self->object->id);
+                            }
+                        }
+                    } elsif ($self->base->get_object('aliases', $self->object->id)) {
+                        if (my $res = $self->base->_delete_object('aliases', $self->object->id)) {
+                            return $res;
+                        } else {
+                            $self->base->log(LA_ERR, "Cannot remove forward for %s",
+                                $self->object->id);
+                        }
+                    } else {
+                        return 1;
+                    }
+                    return;
+                },
+                label => l('Forward'),
+            },
+            aliases   => {
+                #reference => 'aliases',
+                delayed => 1,
+                formtype => 'TEXT',
+                multiple => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $sth = $self->base->db->prepare(q{
+                        select name from aliases where internobject = false and lower($1) =
+                        lower(array_to_string("forward", ','))
+                        } . ($self->base->{wexported} ? '' : 'and exported = true'));
+                    $sth->execute($self->object->id);
+                    my @values;
+                    while (my $res = $sth->fetchrow_hashref) {
+                        push(@values, $res->{name});
+                    }
+                    return \@values;
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+
+                    my $res = 0;
+                    my %aliases = map { $_ => 1 } grep { $_ } (ref $data ? @{$data} : $data);
+                    foreach ($self->object->_get_attributes('aliases')) {
+                        $aliases{$_} ||= 0;
+                        $aliases{$_} +=2;
+                    }
+                    foreach (keys %aliases) {
+                        if ($aliases{$_} == 2) {
+                            if ($self->base->_delete_object('aliases', $_)) {
+                                $res++
+                            } else {
+                                $self->base->log(LA_ERR,
+                                    "Cannot remove aliases %s from user %s", $_,
+                                    $self->object->id);
+                            }
+                        } elsif ($aliases{$_} == 1) {
+                            if ($self->base->_create_c_object(
+                                    'aliases', $_,
+                                    forward => [ $self->object->id ],
+                                    description => 'automatically created for ' . $self->object->id,
+                                )) {
+                                $res++
+                            } else {
+                                $self->base->log(LA_ERR, 'Cannot set forward %s to user %s',
+                                    $_, $self->object->id);
+                                return;
+                            }
+                        } # 3 no change
+                    }
+                    $res
+                },
+                label => l('Aliases'),
+            },
+            revaliases => {
+                formtype => 'TEXT',
+                get => sub {
+                    my ($self) = @_;
+
+                    if (my $obj = $self->base->
+                        get_object('revaliases', $self->object->id)) {
+                        return $obj->get_attributes('as');
+                    } else {
+                        return;
+                    }
+                },
+                set => sub {
+                    my ($self, $data) = @_;
+                
+                    my $res = 0;
+                    if ($data) {
+                        if (my $obj = $self->base->
+                            get_object('revaliases', $self->object->id)) {
+                            my $ares = $obj->set_c_fields(
+                                'as' => $data,
+                                'exported' => ($self->object->get_attributes('exported') || 0),
+                            );
+                            if (defined($ares)) {
+                                $res+=$ares;
+                            } else {
+                                $self->base->log(LA_ERR, 'Cannot set revaliases for user %s',
+                                    $self->object->id);
+                            }
+                        } else {
+                            if ($self->base->_create_c_object(
+                                    'revaliases',
+                                    $self->object->id, as => $data,
+                                    'exported' => ($self->object->get_attributes('exported') || 0),
+                                    description => 'automatically created for ' . $self->object->id,
+                                )) {
+                                $res++;
+                            } else {
+                                $self->base->log(LA_ERR, 'Cannot set revaliases for user %s',
+                                    $self->object->id);
+                            }
+                        }
+                    } else {
+                        $self->base->_delete_object('revaliases', $self->object->id);
+                        $res++;
+                    }
+
+                    $res
+                },
+            },
+            manager => {
+                reference => 'user',
+                ro => 1,
+                get => sub {
+                    my ($self) = @_;
+                    if (my $manager = $self->object->_get_c_field('managerContact')) {
+                        return $manager;
+                    } elsif (my $department = $self->object->_get_c_field('department')) {
+                        my $obj = $self->base->get_object('group', $department);
+                        return $obj->_get_c_field('managedBy');
+                    } else {
+                        return;
+                    }
+                },
+                label => l('Responsible'),
+            },
+            department => {
+                reference => 'group',
+                can_values => sub {
+                    $base->search_objects('group', 'sutype=dpmt')
+                },
+                monitored => 1,
+                label => l('Department'),
+            },
+            contratType => {
+                reference => 'group',
+                can_values => sub {
+                    $base->search_objects('group', 'sutype=contrattype')
+                },
+                monitored => 1,
+                label => l('Type of contract'),
+            },
+            site => {
+                reference => 'site',
+                can_values => sub {
+                    $base->search_objects('site')
+                },
+                get => sub {
+                    my ($self) = @_;
+                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
+                        $self->base->get_object('address', $fmainaddress)
+                            ->_get_c_field($self->name);
+                    } else {
+                        return;
+                    }
+                },
+                set => $subsetaddress,
+                label => l('Site'),
+            },
+            co => {
+                get => sub {
+                    my ($self) = @_;
+                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
+                        $self->base->get_object('address', $fmainaddress)
+                            ->_get_c_field($self->name);
+                    } else {
+                        return;
+                    }
+                },
+                set => $subsetaddress,
+            },
+            l => {
+                get => sub {
+                    my ($self) = @_;
+                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
+                        $self->base->get_object('address', $fmainaddress)
+                            ->_get_c_field($self->name);
+                    } else {
+                        return;
+                    }
+                },
+                set => $subsetaddress,
+                label => l('City'),
+            },
+            postalCode => {
+                get => sub {
+                    my ($self) = @_;
+                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
+                        $self->base->get_object('address', $fmainaddress)
+                            ->_get_c_field($self->name);
+                    } else {
+                        return;
+                    }
+                },
+                set => $subsetaddress,
+                label => l('Postal code'),
+            },
+            streetAddress => {
+                formtype => 'TEXTAREA',
+                get => sub {
+                    my ($self) = @_;
+                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
+                        $self->base->get_object('address', $fmainaddress)
+                            ->_get_c_field($self->name);
+                    } else {
+                        return;
+                    }
+                },
+                set => $subsetaddress,
+                label => l('Street'),
+            },
+            postOfficeBox => {
+                get => sub {
+                    my ($self) = @_;
+                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
+                        $self->base->get_object('address', $fmainaddress)
+                            ->_get_c_field($self->name);
+                    } else {
+                        return;
+                    }
+                },
+                set => $subsetaddress,
+                label => l('Post office box'),
+            },
+            st => {
+                get => sub {
+                    my ($self) = @_;
+                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
+                        $self->base->get_object('address', $fmainaddress)
+                            ->_get_c_field($self->name);
+                    } else {
+                        return;
+                    }
+                },
+                set => $subsetaddress,
+            },
+            facsimileTelephoneNumber => {
+                get => sub {
+                    my ($self) = @_;
+                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
+                        $self->base->get_object('address', $fmainaddress)
+                            ->_get_c_field($self->name);
+                    } else {
+                        return;
+                    }
+                },
+                set => $subsetaddress,
+                label => l('Fax number'),
+            },
+            o => {
+                ro => 1,
+                iname => 'company',
+                label => l('Company'),
+            },
+            ou => {
+                iname => 'department',
+                ro => 1,
+                label => l('Department'),
+            },
+            telephoneNumber => {
+                get => sub {
+                    my ($self) = @_;
+                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
+                        $self->base->get_object('address', $fmainaddress)
+                            ->_get_c_field($self->name);
+                    } else {
+                        return;
+                    }
+                },
+                set => $subsetaddress,
+                label => l('Phone number'),
+            },
+            physicalDeliveryOfficeName => {
+                get => sub {
+                    my ($self) = @_;
+                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
+                        $self->base->get_object('address', $fmainaddress)
+                            ->_get_c_field($self->name);
+                    } else {
+                        return;
+                    }
+                },
+                set => $subsetaddress,
+                label => l('Office'),
+            },
+            uid => {
+                iname => 'name',
+                ro => 1,
+                label => l('Login'),
+            },
+            cn =>  { iname => 'name', ro => 1 },
+            gecos => {
+                managed => 1,
+                ro => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $obj = $self->object;
+
+                    my $ec = $obj->_get_c_field('endcircuit') || '';
+
+                    my $date = $ec
+                        ? sprintf('%s (%s)', $obj->_get_c_field('expireTextEC'), $obj->_get_c_field('expireText'))
+                        : $obj->_get_c_field('expireText');
+
+                    $date ||= '';
+
+                    my $gecos = sprintf("%s,%s,%s,%s",
+                        join(' ', grep { $_ }
+                                ($obj->_get_c_field('givenName'),
+                                ($obj->_get_c_field('sn'))))
+                            || $obj->_get_c_field('description') || '',
+                        join(' - ', grep { $_ } (($obj->_get_c_field('site') ||
+                                    $obj->_get_c_field('l')),
+                            $obj->_get_c_field('physicalDeliveryOfficeName'))) || '',
+                        $obj->_get_c_field('telephoneNumber') || '',
+                        $date,
+                    );
+                    $gecos =~ s/:/ /g;
+                    return to_ascii($gecos);
+                },
+                label => l('GECOS'),
+            },
+            displayName  => {
+                ro => 1, managed => 1,
+                get => sub {
+                    my ($self) = @_;
+                    return join(' ', grep { $_ } 
+                        (
+                            $self->object->_get_c_field('givenName'),
+                            $self->object->_get_c_field('sn')
+                        )
+                    )
+                    || $self->object->_get_c_field('description')
+                    || $self->object->id;
+                },
+                label => l('Name'),
+            },
+            sAMAccountName  => {
+                ro => 1,
+                managed => 1,
+                iname => 'name',
+                get => sub {
+                    my ($self) = @_;
+                    substr($self->object->id, 0, 19)
+                },
+            },
+            accountExpires => {
+                ro => 1,
+                managed => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $obj = $self->object;
+                    my $sth = $obj->db->prepare_cached(
+                        sprintf(
+                            q{select extract(epoch from COALESCE(endcircuit,  expire)) + 11644474161 as expire
+                            from %s where %s = ?},
+                            $obj->db->quote_identifier($obj->_object_table),
+                            $obj->db->quote_identifier($obj->_key_field),
+                        )
+                    );
+                    $sth->execute($obj->id);
+                    my $res = $sth->fetchrow_hashref;
+                    $sth->finish;
+                    return $res->{expire} ? sprintf("%.f", $res->{expire} * 1E7) : '9223372036854775807';
+                }
+            },
+            shadowExpire => {
+                ro => 1,
+                managed => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $obj = $self->object;
+                    my $sth = $obj->db->prepare_cached(
+                        sprintf(
+                            q{select justify_hours(COALESCE(endcircuit,  expire) - '1/1/1970'::timestamp) as expire
+                            from %s where %s = ?},
+                            $obj->db->quote_identifier($obj->_object_table),
+                            $obj->db->quote_identifier($obj->_key_field),
+                        )
+                    );
+                    $sth->execute($obj->id);
+                    my $res = $sth->fetchrow_hashref;
+                    $sth->finish;
+                    return -1 unless($res->{expire});
+                    $res->{expire} =~ /(\d+) days\s*(\w)?/;
+                    # Add one day is time is not 00H00
+                    return $1 + ($2 ? 1 : 0);
+                }
+            },
+            directReports => {
+                reference => 'user',
+                ro => 1,
+                delayed => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $obj = $self->object;
+                    my $sth = $obj->db->prepare_cached(
+                        q{
+                        SELECT
+                        "user".name FROM
+                        public."user",
+                        public.user_attributes_groups,
+                        public.group_attributes_users,
+                        public.group_attributes_base gb,
+                        public."group"
+                        WHERE
+                        "user".ikey = user_attributes_groups.okey AND
+                        user_attributes_groups.value = "group".name AND
+                        group_attributes_users.okey = gb.okey AND
+                        "group".ikey = group_attributes_users.okey AND
+                        gb.attr = 'sutype' AND
+                        gb.value = 'dpmt' AND
+                        group_attributes_users.attr = 'managedBy' AND
+                        group_attributes_users.value = ?
+                        } . ($self->base->{wexported} ? '' : ' and "user".exported = true') . q{
+                            and "user".ikey not in
+                        (select okey from user_attributes_users
+                        where attr = 'manager' and value != ? )
+                        union
+                        select "user".name FROM public."user",
+                        user_attributes_users where
+                        user_attributes_users.attr = 'manager' and user_attributes_users.value = ?
+                            and "user".ikey = user_attributes_users.okey
+                        } . ($self->base->{wexported} ? '' : ' and "user".exported = true')
+                    );
+                    $sth->execute($obj->id, $obj->id, $obj->id);
+                    my @res;
+                    while (my $res = $sth->fetchrow_hashref) {
+                        push(@res, $res->{name});
+                    }
+                    return \@res;
+                },
+            },
+            managedObjects => { ro => 1, reference => 'group', },
+            otheraddress => {
+                ro => 1,
+                reference => 'address',
+                get => sub {
+                    my ($self) = @_;
+                    my $sth = $self->base->db->prepare_cached(q{
+                        select name from address left join address_attributes
+                        on address.ikey = address_attributes.okey and
+                        address_attributes.attr = 'isMainAddress'
+                        where "user" = ?
+                        order by address_attributes.attr
+                        });
+                    $sth->execute($self->object->id);
+                    my @values;
+                    while (my $res = $sth->fetchrow_hashref) {
+                        push(@values, $res->{name});
+                    }
+                    return \@values;
+                },
+            },
+            mainaddress => {
+                ro => 1,
+                reference => 'address',
+                get => sub {
+                    my ($self) = @_;
+                    my $sth = $self->base->db->prepare_cached(q{
+                        select name from address join address_attributes on ikey = okey
+                        where "user" = ? and attr = 'isMainAddress'
+                        });
+                    $sth->execute($self->object->id);
+                    my $res = $sth->fetchrow_hashref;
+                    $sth->finish;
+                    return $res->{name};
+                },
+            },
+            postalAddress => {
+                ro => 1,
+                get => sub {
+                    my ($self) = @_;
+                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
+                        $self->base->get_object('address', $fmainaddress)
+                            ->_get_c_field($self->name);
+                    } else {
+                        return;
+                    }
+                },
+                label => l('Postal Address'),
+            },
+            facsimileTelephoneNumber => {
+                ro => 1,
+                label => l('Fax number'),
+            },
+            allsite   => {
+                ro => 1,
+                reference => 'site',
+            },
+            managerContact => {
+                delayed => 1,
+                can_values => sub {
+                    my %uniq = map { $_ => 1 } grep { $_ }
+                    (($_[1] ? $_[1]->get_attributes('managerContact') : ()),
+                    $base->search_objects('user', 'active=1'));
+                    sort keys %uniq;
+                },
+                reference => 'user',
+                monitored => 1,
+                iname => 'manager',
+                label => l('Manager'),
+            },
+            expireTextEC => {
+                ro => 1,
+                managed => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $obj = $self->object;
+                    my $sth = $obj->db->prepare_cached(
+                        sprintf(
+                            q{select to_char(COALESCE(endcircuit,  expire), 'YYYY/MM/DD') as expire
+                            from %s where %s = ?},
+                            $obj->db->quote_identifier($obj->_object_table),
+                            $obj->db->quote_identifier($obj->_key_field),
+                        )
+                    );
+                    $sth->execute($obj->id) or $obj->db->rollback;
+                    my $res = $sth->fetchrow_hashref;
+                    $sth->finish;
+                    return $res->{expire}
+                },
+            },
+            expireText => {
+                ro => 1,
+                managed => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $obj = $self->object;
+                    my $sth = $obj->db->prepare_cached(
+                        sprintf(
+                            q{select to_char(expire, 'YYYY/MM/DD') as expire
+                            from %s where %s = ?},
+                            $obj->db->quote_identifier($obj->_object_table),
+                            $obj->db->quote_identifier($obj->_key_field),
+                        )
+                    );
+                    $sth->execute($obj->id) or $obj->db->rollback;
+                    my $res = $sth->fetchrow_hashref;
+                    $sth->finish;
+                    return $res->{expire}
+                },
+            },
+            krb5ValidEnd => {
+                ro => 1,
+                managed => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $sth = $self->object->db->prepare_cached(
+                        sprintf(
+                            q{select date_part('epoch', COALESCE(endcircuit,  expire))::int as expire
+                            from %s where %s = ?},
+                            $self->object->db->quote_identifier($self->object->_object_table),
+                            $self->object->db->quote_identifier($self->object->_key_field),
+                        )
+                    );
+                    $sth->execute($self->object->id) or $self->object->db->rollback;
+                    my $res = $sth->fetchrow_hashref;
+                    $sth->finish;
+                    return $res->{expire}
+                },
+            },
+            cells  => {
+                ro => 1,
+                reference => 'group',
+            },
+            departments => {
+                reference => 'group',
+                delayed => 1,
+                ro => 1,
+                label => l('Departments'),
+            },
+            arrivalDate => { },
+            expired => {
+                ro => 1,
+                label => l('Expired'),
+            },
+            active => {
+                ro => 1,
+                label => l('Active'),
+            },
+            status => {
+                ro => 1,
+                label => l('Statut du compte'),
+            },
+            pwdAccountLockedTime => {
+                managed => 1,
+                ro => 1,
+                get => sub {
+                    my ($self) = @_;
+                    my $obj = $self->object;
+                    if ($obj->_get_c_field('locked')) {
+                        return '000001010000Z';
+                    } else {
+                        my $sth = $obj->db->prepare_cached(
+                            sprintf(
+                                q{select to_char(COALESCE(endcircuit,  expire) AT TIME ZONE 'Z', 'YYYYMMDDHH24MISSZ') as expire
+                                from %s where %s = ? and expire < now()},
+                                $obj->db->quote_identifier($obj->_object_table),
+                                $obj->db->quote_identifier($obj->_key_field),
+                            )
+                        );
+                        $sth->execute($obj->id);
+                        my $res = $sth->fetchrow_hashref;
+                        $sth->finish;
+                        return $res->{expire}
+                    }
+                },
+            },
+            userPassword => { readable => 0, },
+            wWWHomePage => {
+                label => l('Web Page'),
+                formopts => { length => 35 },
+            },
+            title => { },
+            snNative => {
+                label => l('Native name'),
+            },
+            givenNameNative => {
+                label => l('Native first name'),
+            },
+            sn => {
+                label => l('Name'),
+            },
+            shadowWarning => { },
+            shadowMin => { },
+            shadowMax => { },
+            shadowLastChange => { },
+            shadowInactive => { },
+            shadowFlag => { },
+            otherTelephone => { },
+            nickname => {
+                label => l('Nickname'),
+            },
+            mobile => { },
+            mail => {
+                label => l('Email'),
+            },
+            otherEmail => {
+                label => l('External mail'),
+            },
+            labeledURI => { },
+            jobType => { },
+            ipPhone => { },
+            initials => {
+                label => l('Initials'),
+                checkinput => sub {
+                    $_[0] or return;
+                    return(length($_[0]) <= 6)
+                }
+            },
+            homePhone => { },
+            homeDirectory => {
+                label => l('Home directory'),
+            },
+            halReference => {
+                label => l('HAL id'),
+            },
+            grade => { },
+            givenName => {
+                label => l('First name'),
+            },
+            encryptedPassword => { },
+            description => {
+                label => l('Description'),
+            },
+            company => {
+                label => l('Company'),
+            },
+            employer => {
+                label => l('Employer'),
+            },
+            comment => {
+                label => l('Comment'),
+            },
+            college => { },
+            passwordLastSet => {
+                ro => 1,
+                label => l('Password set'),
+            },
+            oldPassword => {
+                multiple => 1,
+            },
+            bannedPassword => {
+                multiple => 1,
+            },
+            sshPublicKey => {
+                multiple => 1,
+                formopts => { length => 45 },
+            },
+            currentEmployment => {
+                managed => 1,
+                ro => 1,
+                reference => 'employment',
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+
+                    my $now = DateTime->now()->iso8601 . 'Z';
+
+                    my $sth = $self->base->db->prepare_cached(
+                        q{
+                        select name from employment where firstday <= ?::timestamp and
+                        (lastday is null or lastday >= ?::timestamp - '1 days'::interval) and "user" = ?
+                        limit 1
+                        }
+                    );
+                    $sth->execute($now, $now, $self->id);
+                    my $res = $sth->fetchrow_hashref;
+                    $sth->finish;
+                    if ($res) {
+                        return $res->{name}
+                    } else {
+                        return;
+                    }
+                },
+            },
+            nextEmployment => {
+                managed => 1,
+                ro => 1,
+                reference => 'employment',
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+
+                    my $now = DateTime->now()->iso8601 . 'Z';
+
+                    my $sth = $self->base->db->prepare_cached(
+                        q{
+                        select name from employment where firstday > ?::timestamp and "user" = ?
+                        order by firstday asc
+                        limit 1
+                        }
+                    );
+                    $sth->execute($now, $self->id);
+                    my $res = $sth->fetchrow_hashref;
+                    $sth->finish;
+                    if ($res) {
+                        return $res->{name}
+                    } else {
+                        return;
+                    }
+                }
+            },
+            prevEmployment => {
+                managed => 1,
+                ro => 1,
+                reference => 'employment',
+                get => sub {
+                    my ($attr) = @_;
+                    my $self = $attr->object;
+
+                    my $now = DateTime->now()->iso8601 . 'Z';
+
+                    my $sth = $self->base->db->prepare_cached(
+                        q{
+                        select name from employment where
+                        (lastday is not null and lastday <= ?::timestamp - '1 days'::interval) and "user" = ?
+                        order by firstday desc
+                        limit 1
+                        }
+                    );
+                    $sth->execute($now, $self->id);
+                    my $res = $sth->fetchrow_hashref;
+                    $sth->finish;
+                    if ($res) {
+                        return $res->{name}
+                    } else {
+                        return;
+                    }
+                }
+            },
+            appliedEmployement => {
+                hide => 1,
+                reference => 'employment',
+            },
+            contratTypeHistory => {
+                reference => 'group',
+                can_values => sub {
+                    $base->search_objects('group', 'sutype=contrattype')
+                },
+                multiple => 1,
+            },
+            employmentHistory => {
+                reference => 'employment',
+                multiple => 1,
+                ro => 1,
+            },
+            hosted => {
+                formtype => 'CHECKBOX',
+                label => l('Hosted'),
+            },
+            createRequestId => {
+                label => l('Account Request id')
+            },
+            requestId => {
+                label => l('Request id')
+            }
+
+    };
+
+    my $employmentro = sub {
+        my $setting = $base->config('employment_lock_user') || 'any';
+
+        for ($setting) {
+            /^always$/ and return 1;
+            /^never$/ and return 0;
+
+            $_[0] or return 0;
+
+            /^any$/i and return $_[0]->listEmployment ? 1 : 0;
+            /^active/i and do {
+                return $_[0]->_get_c_field('currentEmployment')
+                ? 1
+                : $_[0]->_get_c_field('nextEmployment') ? 1 : 0;
+            };
+            /(\S+)=(\S+)/ and do {
+                my $attr = $_[0]->_get_c_field($1);
+                if (defined($attr)) {
+                    if ($2 eq '*') {
+                        return 1;
+                    } elsif($2 eq $attr) {
+                        return 1;
+                    } else {
+                        return 0;
+                    }
+                } else {
+                    return 0;
+                }
+            };
+        }
+        return $_[0]->listEmployment ? 1 : 0; # default is any!
+    };
+
+    foreach (qw(contratType managerContact company endcircuit department hosted requestId
+                contratTypeHistory employer)) {
+        $attrs->{$_}{ro} = $employmentro;
+    }
+
+    $attrs->{expire}{ro} = sub {
+        my $expireOn = $base->config('expireOn') || '';
+        if ($expireOn eq 'never') {
+            return 0;
+        }
+
+        $employmentro->($_[0]);
+    };
+
+    $class->SUPER::_get_attr_schema($base, $attrs)
+}
+
+sub CreateAlias {
+    my ($class, $base, $name, $for) = @_;
+
+    my $stAddAlias = $base->db->prepare_cached(
+        q{INSERT INTO "user" (name, uidnumber,            gidnumber, oalias, oaliascache) values
+                             (?,    -nextval('ikey_seq'), ?,         ?,      ?)}
+    );
+
+    my $ref = $base->_derefObject($class->type, $for);
+    my $res = $stAddAlias->execute($name, -1, $for, $ref ? $ref->id : undef);
+    return $res ? 1 : 0;
+}
+
+sub _get_state {
+    my ($self, $state) = @_;
+    for ($state) {
+        /^expired$/ and do {
+            my $attribute = $self->attribute('expire');
+            $attribute->check_acl('r') or return;
+            my $sth = $self->db->prepare_cached(
+                q{ select coalesce(expire < now(), false) as exp from "user" 
+                where "user".name = ?}
+            );
+            $sth->execute($self->id);
+            my $res = $sth->fetchrow_hashref;
+            $sth->finish;
+            return $res->{exp} ? 1 : 0;
+        };
+    }
+}
+
+sub set_fields {
+    my ($self, %data) = @_;
+
+    my $old;
+    if (exists($data{department})) {
+        $old = $self->_get_attributes('department');
+    }
+
+    if (($data{department} || '') eq ($old || '')) {
+        # We do not remove the group, there is no change
+        $old = undef;
+    }
+
+    if ($old) {
+        # If the department is no longer a department
+        # we do nothing
+        my @names = $self->base->search_objects('group', 'name=' . $old, 'sutype=dpmt');
+        if (! @names) {
+            $old = undef;
+        }
+    }
+
+    my $res = $self->SUPER::set_fields(%data) or return;
+
+    if ($self->base->config('remove_old_dpmt') && $old) {
+        $self->base->log(LA_DEBUG,
+            "Removing %s from group %s (department change to %s)",
+            $self->id,
+            $old,
+            $data{department} || '');
+        $self->_delAttributeValue('memberOf', $old) or return;
+        $res++;
+    }
+
+    $res
+}
+
+=head2 listEmployment
+
+Return the ordered list of contract
+
+=cut
+
+sub listEmployment {
+    my ($self) = @_;
+
+    my $sth = $self->base->db->prepare_cached(
+        q{
+        select name from employment where "user" = ?
+        order by lastday desc NULLS first
+        }
+    );
+    $sth->execute($self->id);
+    my @list = ();
+    while (my $res = $sth->fetchrow_hashref) {
+        push(@list, $res->{name});
+    }
+
+    @list
+}
+
+sub _reported_atributes { qw(contratType endcircuit hosted requestId company employer) }
+
+=head2 applyCurrentEmployment
+
+Search the current employment is any and apply paramter to user
+
+=cut
+
+sub applyCurrentEmployment {
+    my ($self) = @_;
+
+    # Get current employment name
+    my $currentempl = $self->get_attributes('currentEmployment') || '';
+
+    $self->base->log(
+        LA_DEBUG,
+        "Applying Employement %s to user %s",
+        $currentempl || '(none)',
+        $self->id
+    );
+
+    if (my $currentemployment = $self->base->get_object('employment', $currentempl)) {
+
+        # If an employement apply we set the value to the user object
+
+        $self->computeEmploymentDate;
+
+        my %attrsets = (
+            appliedEmployement => $currentemployment->id,
+        );
+        foreach my $attr (_reported_atributes(), qw(department managerContact)) {
+            my $uval = $self->get_attributes($attr) || '';
+            my $cval = $currentemployment->get_attributes($attr) || '';
+
+            if ($attr eq 'managerContact') {
+                if (!$cval) {
+                    my $dpmt  = $currentemployment->get_attributes('department') or last;
+                    my $odmpt = $currentemployment->base->get_object('group', $dpmt) or last;
+                    $cval = $odmpt->get_attributes('managedBy');
+                }
+            }
+
+            if ($uval ne $cval) {
+                my $oattr = $currentemployment->base->attribute('user', $attr);
+                $attrsets{$oattr->iname} = $cval;
+            }
+        }
+
+        if (keys %attrsets) {
+            if (my $res = $self->set_fields(%attrsets)) {
+                $self->ReportChange('Update', 'Attr %s updated to match Employment %s', join(', ', sort keys %attrsets), $currentemployment->id);
+                return $res;
+            }
+        } else {
+            return 1;
+        }
+    } else {
+        # No current employment, resetting values:
+
+        return $self->_resetEmployment;
+    }
+
+}
+
+# Reset attribute value set by employment
+# except managerContact and expire
+
+sub _resetEmployment {
+    my ($self) = @_;
+
+    $self->computeEmploymentDate;
+
+    my %changes = (
+        appliedEmployement => undef,
+    );
+
+    my @attributesToReset = (_reported_atributes, qw(department));
+
+    foreach my $attr (@attributesToReset) {
+        my $default = $self->base->config("unemployment.$attr") || '';
+        my $old = $self->_get_attributes($attr) || '';
+
+        if ($old ne $default) {
+            $changes{$attr} = $default || undef;
+        }
+    }
+
+    if(!$self->get_attributes('managerContact')) {
+        if (my $next = $self->_get_attributes('nextEmployment')) {
+            my $onext = $self->base->get_object('employment', $next);
+            $changes{'manager'} = $onext->_get_attributes('managerContact');
+        }
+    }
+
+    if (%changes) {
+        if ($self->set_fields(%changes)) {
+            $self->base->log(LA_NOTICE, "Updating user %s to match unemployment", $self->id);
+            $self->ReportChange('Update', 'Update %s to match unemployment', join(', ', sort keys %changes));
+            return 1;
+        } else {
+            return 0;
+        }
+    } else {
+        return 1;
+    }
+
+}
+
+
+=head2 computeEmploymentDate
+
+Compute and copy to user start and end employment date
+
+=cut
+
+sub computeEmploymentDate {
+    my ($self) = @_;
+
+    my $currentemployment = $self->get_attributes('currentEmployment') || '';
+
+    my $expire = str2time($self->_get_attributes('expire') || '1970-01-01T00:00:00');
+
+    my %changes;
+    my @employmentDate = qw(
+        endEmployment   endStrictEmployment   endCurrentEmployment   endLastEmployment
+        startEmployment startStrictEmployment startCurrentEmployment startFirstEmployment
+        arrival departure
+    );
+    foreach (@employmentDate) {
+        my $old = $self->_get_attributes($_) || '';
+        my $new = $self->_get_attributes("_$_") || '';
+        if ($old ne $new) {
+            $changes{$_} = $new || undef;
+        }
+    }
+
+    # If there is no current employment we try to find any to not let expire
+    # unset
+
+    my $expireOn = $self->base->config('expireOn') || '';
+    if (!grep { $_ eq $expireOn } (@employmentDate, '', 'never')) {
+        $self->base->log(LA_ERR, "expireOn set to invalid parameter %s, using endEmployment instead", $expireOn);
+        $expireOn = undef;
+    }
+    $expireOn ||= 'endEmployment';
+
+    # We check if matching start exists to know if using end* for expiration is
+    # safe, even undef
+    my %end2start = (
+        endEmployment        => 'startEmployment',
+        endStrictEmployment  => 'startStrictEmployment',
+        endCurrentEmployment => 'startCurrentEmployment',
+        endLastEmployment    => 'startFirstEmployment',
+    );
+
+    # TODO rework this, working code but bloat
+    if ($expireOn ne 'never') {
+        my $endemploy = '';
+        if ($self->_get_attributes("_$end2start{$expireOn}")) {
+            $endemploy = $self->_get_attributes("_$expireOn") || '';
+            $endemploy ||= 'UNCHANGED' unless($currentemployment);
+        } elsif (($self->base->config('unemployed_expire') ||'') eq 'no') {
+            $endemploy = '';
+        } else {
+            # No expiration date apply, don't touch
+            $endemploy = 'UNCHANGED';
+        }
+
+        if ($endemploy ne 'UNCHANGED') {
+            my $nextexpire = str2time( $endemploy || '1970-01-01T00:00:00' );
+
+            if ($expire != $nextexpire) {
+                $changes{expire} = $endemploy;
+            }
+        }
+    }
+
+    if (keys %changes) {
+        $self->base->log(LA_DEBUG, 'Applying employment state to user %s for field %s', $self->id, join(', ', keys %changes));
+        $self->ReportChange('Update', 'Update %s to match employment', join(', ', sort keys %changes));
+        if (exists($changes{expire})) {
+            $self->ReportChange('Update', 'Expire update to %s to match employment', ($changes{expire} || '(none)'));
+            $self->base->log(LA_DEBUG, 'New expiration is %s for user %s', ($changes{expire} || '(none)'), $self->id);
+        }
+        $self->set_fields(%changes);
+    } else {
+        $self->base->log(LA_DEBUG, 'No employment change for user %s', $self->id);
+    }
+
+    $self->_computeEmploymentHistory();
+
+    return 1;
+}
+
+sub _computeStartEmployment {
+    my ($self, $delay, $any, $workday) = @_;
+
+    $delay ||= 0;
+    my $start;
+    my $nstart;
+
+    if (my $next = $self->_get_attributes('nextEmployment')) {
+        my $onext = $self->base->get_object('employment', $next);
+        $nstart = DateTime->from_epoch(epoch => str2time($onext->_get_attributes('firstday')));
+        $nstart->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+    }
+
+    my $list_empl = $self->base->db->prepare_cached(q{
+        SELECT *, (lastday is null or lastday >= now() - '1days'::interval) as "current" FROM employment
+        WHERE "user" = ? and firstday < now()
+        order by firstday desc
+        });
+    $list_empl->execute($self->id);
+
+    while (my $res = $list_empl->fetchrow_hashref) {
+        if ($res->{current}) {
+        } elsif ($nstart) {
+            my $prevend = DateTime->from_epoch(epoch => str2time($res->{lastday}));
+            $prevend->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+            my $tstart = $nstart->clone;
+            $tstart->subtract(days => $delay + 1);
+            if ($tstart->ymd gt $prevend->ymd) {
+                last;
+            }
+        } elsif ((!$res->{current}) && (!$any)) {
+            last;
+        }
+        $nstart = DateTime->from_epoch(epoch => str2time($res->{firstday}));
+        $nstart->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+        $start = $nstart->clone;
+    }
+    $list_empl->finish;
+
+    $start ||= $nstart if ($any);
+
+    if ($start) {
+        if ($workday) {
+            my $day_of_week = $start->day_of_week;
+            $start->add(days =>
+                $day_of_week == 6 ? 2 :
+                $day_of_week == 7 ? 1 : 0
+            );
+        }
+    }
+
+    return $start ? $start->iso8601 : undef
+}
+
+sub _computeEndEmployment {
+    my ($self, $delay, $any, $workday) = @_;
+
+    $delay ||= 0;
+    my $end;
+    my $pend;
+
+    if (my $prev = $self->_get_attributes('prevEmployment')) {
+        my $oprev = $self->base->get_object('employment', $prev);
+        $pend = DateTime->from_epoch(epoch => str2time($oprev->_get_attributes('lastday')));
+        $pend->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+        $pend->add(hours => 23, minutes => 59, seconds => 59);
+    }
+
+    my $list_empl = $self->base->db->prepare_cached(q{
+        SELECT *, firstday <= now() as "current" FROM employment WHERE "user" = ? and
+        (lastday is null or lastday >= now() - '1 days'::interval)
+        order by firstday asc
+        });
+    $list_empl->execute($self->id);
+    while (my $res = $list_empl->fetchrow_hashref) {
+        if (!$res->{lastday}) {
+            # Ultimate employment.
+            $list_empl->finish;
+            return undef;
+        }
+        if ($res->{current}) {
+        } elsif ($end) {
+            my $nextstart = DateTime->from_epoch(epoch => str2time($res->{firstday}));
+            $nextstart->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+            my $tend = $end->clone;
+            $tend->add(days => $delay + 1);
+            if ($tend->ymd lt $nextstart->ymd) {
+                last;
+            }
+        } elsif ($pend) {
+            my $nextstart = DateTime->from_epoch(epoch => str2time($res->{firstday}));
+            $nextstart->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+            my $tend = $pend->clone;
+            $tend->add(days => $delay + 1);
+            if ($tend->ymd lt $nextstart->ymd) {
+                last;
+            }
+            $end = $tend
+        } elsif ((!$res->{current}) && (!$any)) {
+            last;
+        }
+        $end = DateTime->from_epoch(epoch => str2time($res->{lastday}));
+        $end->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+        $end->add(hours => 23, minutes => 59, seconds => 59);
+    }
+    $list_empl->finish;
+
+    $end ||= $pend if($any);
+
+    if ($end) {
+        if ($workday) {
+            my $day_of_week = $end->day_of_week;
+            $end->subtract(days =>
+                $day_of_week == 7 ? 2 :
+                $day_of_week == 6 ? 1 : 0
+            );
+        }
+    }
+
+    return $end ? $end->iso8601 : undef
+}
+
+# Compute a summary of employement and store the value
+# into an attribute
+
+sub _computeEmploymentHistory {
+    my ($self) = @_;
+
+    my %changes;
+
+    {
+        my $sth = $self->db->prepare_cached(q{
+            select employment.name from employment
+            where "user" = ? and employment.firstday < now()
+        });
+        $sth->execute($self->id);
+        my @values;
+        while (my $res = $sth->fetchrow_hashref) {
+            push(@values, $res->{name});
+        }
+        $changes{"employmentHistory"} = \@values;
+    }
+
+    foreach my $attribute (qw(contratType)) {
+        my $sth = $self->db->prepare_cached(q{
+            select employment_attributes.value from employment
+            join employment_attributes
+            on employment.ikey = employment_attributes.okey
+            where "user" = ? and employment_attributes.attr = ?
+            and employment.firstday < now()
+            group by employment_attributes.value
+            });
+        $sth->execute($self->id, $attribute);
+        my @values;
+        while (my $res = $sth->fetchrow_hashref) {
+            push(@values, $res->{value});
+        }
+        $changes{$attribute . "History"} = \@values;
+    }
+
+    $self->set_fields(%changes);
+}
+
+=head2 storeBannedPassword($epassword)
+
+Add an encrypted password to untrust list
+
+=cut
+
+sub storeBannedPassword {
+    my ($self, $EncPass) = @_;
+
+    my @banned = sort { $b cmp $a } $self->_get_attributes('bannedPassword');
+    my $now = DateTime->now; 
+    unshift(@banned, $now->iso8601 . ';' . $EncPass);
+    $self->set_fields('bannedPassword', [ grep { $_ } @banned ]);
+
+}
+
+=head2 banCurrentPassword
+
+Store the current password as banned
+
+=cut
+
+sub banCurrentPassword {
+    my ($self) = @_;
+    
+    my $old = $self->get_field('userPassword') or return;
+    $self->storeBannedPassword($old);
+}
+
+sub check_password {
+    my ( $self, $password ) = @_;
+
+    my $res = $self->SUPER::check_password($password);
+    if ($res !~ /^ok$/) {
+        return $res;
+    }
+
+    foreach my $banned ($self->_get_attributes('bannedPassword')) {
+        my ($date, $oldPassword) = $banned =~ /^([^;]*);(.*)/;
+        if (crypt($password, $oldPassword) eq $oldPassword) {
+            return "Banned password, cannot be used anymore";
+        }
+    }
+
+    return 'ok';
+}
+
+sub _set_password {
+    my ($self, $clear_pass) = @_;
+    if (my $attr = $self->base->attribute($self->type, 'userPassword')) {
+        my $field = $attr->iname;
+
+        # Storing as old password
+        my @olds = sort { $b cmp $a } $self->_get_attributes('oldPassword');
+        if (my $old = $self->get_field('userPassword')) {
+            my $now = DateTime->now; 
+            unshift(@olds, $now->iso8601 . ';' . $old);
+            $self->set_fields('oldPassword', [ grep { $_ } @olds[0 .. 14] ]);
+        }
+
+        my $res = $self->set_fields($field, $self->base->passCrypt($clear_pass));
+        if ($res) {
+            if ($self->base->get_global_value('rsa_public_key')) {
+                $self->setCryptPassword($clear_pass) or return;
+            }
+        }
+
+        $self->set_fields('passwordLastSet', DateTime->now->datetime);
+        $self->base->log(LA_NOTICE,
+            'Mot de passe changÃ© pour %s',
+            $self->id
+        );
+
+
+        return $res;
+    } else {
+        $self->log(LA_WARN,
+            "Cannot set password: userPassword attributes is unsupported");
+    }
+}
+
+=head2 setCryptPassword($clear_pass)
+
+Store password encrypted using RSA encryption.
+
+=cut
+
+sub setCryptPassword {
+    my ($self, $clear_pass) = @_;
+    if (my $serialize = $self->base->get_global_value('rsa_public_key')) {
+        my $public = Crypt::RSA::Key::Public->new;
+        $public = $public->deserialize(String => [ $serialize ]);
+        my $rsa = new Crypt::RSA ES => 'PKCS1v15';
+        my $rsa_password = $rsa->encrypt (
+            Message    => $clear_pass,
+            Key        => $public,
+            Armour     => 1,
+        ) || die $rsa->errstr();
+        if (!$self->_set_c_fields('encryptedPassword', $rsa_password)) {
+            $self->log(LA_ERR,
+                "Cannot set 'encryptedPassword' attribute for object %s/%s",
+                $self->type, $self->id,
+            );
+            return;
+        }
+    }
+    $self->ReportChange('Password', 'Password stored using internal key');
+    return 1;
+}
+
+=head2 _InjectCryptPasswd($cryptpasswd)
+
+Inject a password encrypted using standard UNIX method.
+
+The passwrod will be used to authenticate user inside the application but it
+will not be transmit to any other database.
+
+=cut
+
+sub _InjectCryptPasswd {
+    my ($self, $cryptpasswd) = @_;
+
+    if (my $current = $self->get_field('userPassword')) {
+        if ($cryptpasswd eq $current) {
+            return 1;
+        }
+    }
+    my $res = $self->set_fields('userPassword', $cryptpasswd);
+
+    if ($res) {
+        $self->base->log(LA_NOTICE, 'Crypted password injected for %s', $self->id);
+        return 1;
+    } else {
+        $self->base->log(LA_ERR, 'Cannot inject crypted password for %s', $self->id);
+        return 0;
+    }
+}
+
+=head2 GenPasswordResetId
+
+Return a new id allowing passowrd reset
+
+=cut
+
+sub GenPasswordResetId {
+    my ($self) = @_;
+
+    my $id = LATMOS::Accounts::Utils::genpassword(length => 32);
+
+    my $sth = $self->base->db->prepare_cached(q{
+        INSERT INTO passwordreset (id, "user") values (?,?)
+    });
+
+    if ($sth->execute($id, $self->id)) {
+        return $id;
+    } else {
+        return;
+    }
+}
+
+=head2 SendPasswordReset($url)
+
+Generate a password reset Id and the to the user.
+
+C<$url> is the URL where the password can changed (printf forward, the %s is
+replaced by the request id)
+
+=cut
+
+sub SendPasswordReset {
+    my ($self, $url) = @_;
+
+    my $id = $self->GenPasswordResetId;
+
+    my $mail = $self->_get_attributes('mail') or do {
+        $self->base->log(LA_ERR, "Cannot sent reset password mail: no mail found");
+        return;
+    };
+
+    my $MailSubject = $self->base->la->val('_default_', 'mailSubject', 'LATMOS::Accounts');
+
+    my %mail = (
+        Subject => "$MailSubject: pasword reset",
+        'X-LATMOS-Reason' => 'Password Reset',
+        to => $mail,
+    );
+
+    if (my $otherEmail = $self->_get_attributes('otherEmail')) {
+        $mail{cc} = $otherEmail;
+    }
+
+    my $vars = {
+        url => sprintf($url, $id),
+    };
+    $vars->{id} =  $id;
+    $vars->{obj} = $self;
+
+    my $lamail = LATMOS::Accounts::Mail->new(
+        $self->base->la,
+        'passwordreset.mail',
+    );
+
+    if ($lamail->process(\%mail, $vars)) {
+        $self->base->log(LA_NOTICE,
+            "Reset password sent to %s for user %s",
+            $mail{to},
+            $self->id,
+        );
+        return 1;
+    } else {
+        return;
+    }
+
+}
+
+=head2 CheckPasswordResetId($id)
+
+Return True if the reset password ID can be found and is less than one day old
+
+=cut
+
+sub CheckPasswordResetId {
+    my ($self, $id) = @_;
+
+    my $sth = $self->base->db->prepare_cached(q{
+        SELECT * FROM passwordreset WHERE
+            "user" = ? and
+            id = ? and
+            "create" >= now() - '1 days'::interval
+    });
+    $sth->execute($self->id, $id);
+
+    my $res = $sth->fetchrow_hashref;
+    $sth->finish;
+
+    return $res ? 1 : 0;
+}
+
+=head2 DeletePasswordId($id) 
+
+Delete password reset C<$id> and all expired request
+
+=cut
+
+sub DeletePasswordId {
+    my ($self, $id) = @_;
+
+    my $sth = $self->base->db->prepare_cached(q{
+        DELETE FROM passwordreset WHERE
+        "user" = ? AND (id = ? or "create" < now() - '1 days'::interval)
+    });
+
+    $sth->execute($self->id, $id);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Userstatus.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Userstatus.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/Userstatus.pm	(revision 2119)
@@ -0,0 +1,108 @@
+package LATMOS::Accounts::Bases::Sql::Userstatus;
+
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Sql::Employment);
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::Userstatus - Virtual object about only futur employment
+
+=head1 DESCRIPTION
+
+This module handle object for office address, allowing to support multiple
+address per people.
+
+=cut
+
+sub list {
+    my ($class, $base) = @_;
+
+    my $sth = $base->db->prepare_cached(
+        sprintf(
+            q{select %s.%s as k from %s
+              join "user" on %s."user" = "user".name
+              where (%s.lastday is null or %s.lastday >= now() - '1 days'::interval)
+              %s %s
+              order by %s.%s
+            },
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_key_field),
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_object_table),
+            ($base->{wexported} ? '' : 'and "user".exported = true'),
+            ($base->{wexported} ? '' : 'and ' .
+                $base->db->quote_identifier($class->_object_table) . '.exported = true'),
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_key_field),
+        )
+    );
+    $sth->execute;
+    my @keys;
+    while(my $res = $sth->fetchrow_hashref) {
+        push(@keys, $res->{k});
+    }
+    @keys
+}
+
+sub list_from_rev {
+    my ($class, $base, $rev) = @_;
+
+    my $sth = $base->db->prepare_cached(
+        sprintf(
+            q{select %s.%s as k from %s
+              join "user" on %s."user" = "user".name
+              where (%s.lastday is null or %s.lastday >= now() - '1 days'::interval)
+              and rev > ?
+              %s %s
+              order by %s.%s
+            },
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_key_field),
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_object_table),
+            ($base->{wexported} ? '' : 'and "user".exported = true'),
+            ($base->{wexported} ? '' : 'and ' .
+                $base->db->quote_identifier($class->_object_table) . '.exported = true'),
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_key_field),
+        )
+    );
+    $sth->execute($rev);
+    my @keys;
+    while(my $res = $sth->fetchrow_hashref) {
+        push(@keys, $res->{k});
+    }
+    @keys
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>
+
+L<LATMOS::Accounts::Bases::Sql::Address>
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009, 2010, 2011, 2012 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/objects.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/objects.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/objects.pm	(revision 2119)
@@ -0,0 +1,1428 @@
+package LATMOS::Accounts::Bases::Sql::objects;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Objects);
+use LATMOS::Accounts::Log;
+use Crypt::RSA;
+use Crypt::RSA::Key::Public::SSH;
+use DateTime;
+use LATMOS::Accounts::I18N;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Sql::objects - Parent class for SQL object
+
+=cut
+
+sub _attributes_table { $_[0]->_object_table . '_attributes_list' }
+
+# Write extend attribute table
+sub _object_table_attributes      { $_[0]->_object_table . '_attributes' }
+# Read search table (with compute attribute)
+sub _object_table_attributes_read { $_[0]->_object_table . '_attributes' }
+
+sub listReal {
+    my ($class, $base) = @_;
+
+    my $sth = $base->db->prepare_cached(
+        sprintf(
+            q{select %s as k from %s where internobject = false
+                and oalias IS NULL %s order by %s},
+            $base->db->quote_identifier($class->_key_field),
+            $base->db->quote_identifier($class->_object_table),
+            ($base->{wexported} ? '' : 'and exported = true'),
+            $base->db->quote_identifier($class->_key_field),
+        )
+    );
+    $sth->execute;
+    my @keys;
+    while(my $res = $sth->fetchrow_hashref) {
+        push(@keys, $res->{k});
+    }
+    @keys
+}
+
+sub list {
+    my ($class, $base) = @_;
+
+    my $sth = $base->db->prepare_cached(
+        sprintf(
+            q{select %s as k from %s where internobject = false %s order by %s},
+            $base->db->quote_identifier($class->_key_field),
+            $base->db->quote_identifier($class->_object_table),
+            ($base->{wexported} ? '' : 'and exported = true'),
+            $base->db->quote_identifier($class->_key_field),
+        )
+    );
+    $sth->execute;
+    my @keys;
+    while(my $res = $sth->fetchrow_hashref) {
+        push(@keys, $res->{k});
+    }
+    @keys
+}
+
+sub list_from_rev {
+    my ($class, $base, $rev) = @_;
+    my $sth = $base->db->prepare_cached(
+        sprintf(
+            q{select %s as k from %s where rev > ? and internobject = false
+                and oalias IS NULL  %s order by %s},
+            $base->db->quote_identifier($class->_key_field),
+            $base->db->quote_identifier($class->_object_table),
+            ($base->{wexported} ? '' : 'and exported = true'),
+            $base->db->quote_identifier($class->_key_field),
+        )
+    );
+    $sth->execute($rev);
+    my @keys;
+    while(my $res = $sth->fetchrow_hashref) {
+        push(@keys, $res->{k});
+    }
+    @keys
+}
+
+sub _has_extended_attributes { 0 }
+
+sub _get_attr_schema {
+    my ($class, $base, $info) = @_;
+    $info ||= {};
+    if (!$base->{__cache}{$class->_object_table}{inline}) {
+        $base->{__cache}{$class->_object_table}{inline} = [];
+        my $sth = $base->db->prepare(
+            q{SELECT column_name FROM information_schema.columns
+              WHERE table_name = ?}
+        );
+        $sth->execute($class->_object_table);
+        while (my $res = $sth->fetchrow_hashref) {
+            push(@{$base->{__cache}{$class->_object_table}{inline}},
+                $res->{column_name});
+        }
+    }
+    foreach (@{$base->{__cache}{$class->_object_table}{inline}}) {
+        $info->{$_}{inline} = 1;
+        if (m/^(rev|date|create|ikey)$/) {
+            $info->{$_}{ro} = 1
+        }
+    }
+
+    # Common to all object attribute:
+    my %commons = (
+        name      => {
+            inline => 1,
+            ro => 1,
+            label => l('Name'),
+        },
+        create    => {
+            inline => 1,
+            ro => 1,
+            label => l('Created'),
+        },
+        date      => {
+            inline => 1,
+            ro => 1,
+            label => l('Last modified'),
+        },
+        exported   =>   { inline => 1, formtype => 'CHECKBOX', hide => 1, monitored => 1 },
+        unexported =>   {
+            inline => 1,
+            managed => 1,
+            formtype => 'CHECKBOX',
+            get => sub {
+                my ($self) = @_;
+                return $self->object->get_field('exported') ? undef : 1;
+            },
+            set => sub {
+                my ($self, $data) = @_;
+                $self->object->_set_c_fields('exported', $data ? 0 : 1);
+            },
+            label => l('Hidden'),
+        },
+        services   =>   {
+            managed => 1,
+            multiple => 1,
+            reference => 'service',
+            label => l('Service'),
+        },
+        modifiedby =>   {
+            inline  => 1,
+            reference => 'user',
+            ro => 1,
+            label => l('Modified by'),
+        },
+        createdby => {
+            inline  => 1,
+            reference => 'user',
+            ro => 1,
+            label => l('Created by'),
+        },
+        oalias => {
+            inline  => 1,
+            reference => $class->type,
+            label => l('Object alias for'),
+            post => sub {
+                my ($self, $value) = @_;
+                $self->object->_update_aliases_ptr();
+                $self->object->_update_aliases_cache();
+            },
+            checkinput => sub {
+                my ($oalias) = @_;
+                my $otype = $class->type;
+                $oalias ||= '';
+
+                if (my ($aliasotype, $aliasoname, $aliasattr) = $oalias =~ m/^([^\/]+)\.([^\.]+)\.(.*)$/) {
+                    my $attribute = $base->attribute($aliasotype, $aliasattr) or do {
+                        $base->log(LA_DEBUG, "Oalias %s (%s): can fetch attibute %s/%s",
+                            $otype, $oalias, $aliasotype, $aliasattr);
+                        return;
+                    };
+                    my $refotype = $attribute->reference or do {
+                        $base->log(LA_DEBUG, "Oalias %s (%s): Attribute does not reference an object",
+                            $otype, $oalias);
+                        return;
+                    };
+
+                    if ($attribute->multiple) {
+                        $base->log(LA_DEBUG, "Oalias %s (%s): Attribute must not be multiple",
+                            $otype, $oalias);
+                        return;
+                    };
+                } elsif(!$base->get_object($otype, $oalias)) {
+                    $base->log(LA_DEBUG, "Cannot get object $otype/$oalias");
+                    return;
+                }
+                return 1;
+            },
+        },
+        oaliascache => {
+            inline  => 1,
+            reference => $class->type,
+            label => l('Cache for oalias data'),
+            hide => 1,
+        },
+        internobject => {
+            inline  => 1,
+            label => l('Internal object'),
+            hide => 1,
+        },
+        nodelete => {
+            inline => 1,
+            label => l('Deletion protection'),
+            hide => 1,
+        },
+        setReftoMe  => {
+            multiple => 1,
+            managed => 1,
+            delayed => 1,
+            set => sub {
+                my ($self, $data) = @_;
+                my $count = 0;
+                foreach (ref $data ? @{$data} : $data) {
+                    my ($otype, $oname, $attribute) = $_ =~ m/^([^\.]+)\.(.+)\.([^\.]+)$/ or next;
+                    my $object = $base->get_object($otype, $oname) or next;
+                    my $attr = $base->attribute($otype, $attribute) or next;
+                    if ($attr->reference ne $self->object->type) {
+                        $base->log(LA_ERR, "Cannot set $_ pointing to " . $self->object->id . ": attribute does not reference " . $self->object->type);
+                        return;
+                    }
+                    if ($attr->multiple) {
+                        my @values = $object->get_attributes($attribute);
+                        $count += $object->set_c_fields($attribute => [ @values, $self->object->id ]);
+                    } else {
+                        $count += $object->set_c_fields($attribute => $self->object->id);
+                    }
+                }
+                return $count;
+            },
+            label => l('Set me as reference'),
+        },
+    );
+
+    # Merging / overriding with common to all object attributes properties
+    foreach my $attr (keys %commons) {
+        foreach my $var (keys %{ $commons{$attr} }) {
+            $info->{$attr}{$var} = $commons{$attr}{$var};
+        }
+    }
+
+    # TODO kill this code: useless since everything is declared in perl code
+    if ($class->_has_extended_attributes) {
+        if (!$base->{__cache}{$class->_object_table}{extend}) {
+            $base->{__cache}{$class->_object_table}{extend} = [];
+            my $sth = $base->db->prepare_cached(
+                sprintf(
+                    q{select canonical from %s order by canonical},
+                    $base->db->quote_identifier($class->_attributes_table),
+                )
+            );
+            $sth->execute;
+            while (my $res = $sth->fetchrow_hashref) {
+                push(@{$base->{__cache}{$class->_object_table}{extend}},
+                        $res->{canonical});
+            }
+        }
+        foreach (@{$base->{__cache}{$class->_object_table}{extend}}) {
+            #$base->log(LA_DEBUG, 'Attribute %s for %s not declared in code', $_, $class->type) if(!exists($info->{$_}));
+            $info->{$_} ||= {};
+        }
+    }
+
+    $info
+}
+
+# Everything managed by the perl code
+
+sub _managed_fields {
+    my ($class, $for, $base) = @_;
+    return();
+}
+
+sub new {
+    my ($class, $base, $id) = @_;
+
+    my $__cache = $base->{__cache}{"_" . $class->type};
+
+    if (!(exists($__cache->{$id})
+        && $__cache->{$id}{__time} >= time - 1)) {
+
+        my $sth = $base->db->prepare_cached(
+            sprintf(q{ select 1 from %s where %s = ? %s},
+                $base->db->quote_identifier($class->_object_table),
+                $base->db->quote_identifier($class->_key_field),
+                ($base->{wexported} ? '' : 'and exported = true'),
+            ),
+        );
+        my $count = $sth->execute($id);
+        $sth->finish;
+        ($count || 0) == 1 or return;
+    }
+    $class->SUPER::new($base, $id);
+}
+
+sub Iid { $_[0]->_get_ikey }
+
+sub _get_ikey {
+    my ($class, $base, $id) = @_;
+    $base ||= $class->base;
+    $id ||= $class->id;
+    my $sth = $base->db->prepare_cached(
+        sprintf(
+            q{select ikey from %s where %s = ?},
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_key_field),
+        )
+    );
+    $sth->execute($id);
+    my $res = $sth->fetchrow_hashref;
+    $sth->finish;
+    $res->{ikey}
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+
+    # splitting inline from extended
+    my (%first, %second);
+    # Ensure object is exported if not specified
+    $data{exported} = 1 if (!exists($data{exported}));
+    if (exists $data{unexported}) {
+        $data{exported} = $data{unexported} ? 0 : 1;
+        delete $data{unexported}
+    }
+    foreach (keys %data) {
+        my $attr = $base->attribute($class->type, $_) or next;
+        $_ =~ /^exported$/ and $data{$_} = $data{$_} ? 1 : 0;
+        my $formatted = ref($data{$_})
+            ? [ map { $attr->input($_) } @{ $data{$_} } ]
+            : $attr->input($data{$_});
+        if ($attr->{inline} && ! $attr->{delayed}) {
+            my $iname = $attr->iname;
+            $first{$iname} = $formatted;
+        } else {
+            # the real internal name will be translate by _set_c_field
+            $second{$_} = $formatted if(defined($formatted));
+        }
+    }
+    $first{$class->_key_field} = $id;
+    $first{createdby} =  $base->user || '@Console';
+    $first{modifiedby} = $base->user || '@Console';
+
+    my $lastid;
+    {
+        my $sthnextval = $base->db->prepare_cached("select nextval('ikey_seq') as c");
+        $sthnextval->execute;
+        $lastid = $sthnextval->fetchrow_hashref()->{c};
+        $first{ikey} = $lastid;
+        $sthnextval->finish;
+    }
+
+    my $sth = $base->db->prepare(
+        sprintf(
+            q{insert into %s (%s) values (%s)},
+            $base->db->quote_identifier($class->_object_table),
+            join(', ', map { $base->db->quote_identifier($_) } sort keys %first),
+            join(',', qw(?) x scalar(keys %first)),
+        )
+    );
+    $sth->execute(map { defined($first{$_}) ? $first{$_} : undef } sort keys %first) or return;
+
+    my $sthid = $base->db->prepare_cached(
+        sprintf(q{select %s as k from %s where ikey = ?},
+            $base->db->quote_identifier($class->_key_field),
+            $base->db->quote_identifier($class->_object_table),
+        )
+    );
+    $sthid->execute($lastid);
+    my $res = $sthid->fetchrow_hashref();
+    $sthid->finish;
+    $res or do {
+        $base->log(LA_DEBUG, 'Cannot retrieve SQL row from freshly create object %s/%s', $class->type, $id);
+        return;
+    };
+
+    my $obj = $class->new($base, $res->{k}) or return;
+    if (keys %second) {
+        $obj->_set_c_fields(%second) or do {
+            $base->log(LA_DEBUG, 'Cannot set attributes to freshly create object %s/%s', $class->type, $id);
+            return;
+        };
+    }
+
+    return $res->{k};
+}
+
+=head2 refreshRev
+
+Increase revision of the object to force synchronisation
+
+=cut
+
+sub refreshRev {
+    my ($self) = @_;
+
+    my $sth = $self->db->prepare_cached(
+        sprintf(q{
+                UPDATE %s SET rev = nextval('revisions_rev_seq'::regclass)  WHERE %s = ?
+            },
+            $self->db->quote_identifier($self->_object_table),
+            $self->db->quote_identifier($self->_key_field),
+        )
+    );
+    $sth->execute($self->id);
+}
+
+=head2 CreateAlias($base, $name, $for)
+
+Create an alias named C<$name> with pointing to C<$for>
+
+=cut
+
+sub CreateAlias {
+    my ($class, $base, $name, $for) = @_;
+
+    $base->log(LA_ERR, '%s does not support alias object, alias %s not created', $class->type, $name);
+}
+
+sub _delete {
+    my ($class, $base, $id) = @_;
+
+    my $__cache = $base->{__cache}{"_" . $class->type};
+
+    my $obj = $base->get_object($class->type, $id)
+        or return;
+
+    if ($obj->_get_attributes('internobject')) {
+        # Cannot happend: internal are not fetchable
+        $base->log(LA_ERR,'Cannot delete %s/%s: is an internal object', $class->type, $id);
+        return;
+    }
+    if ($obj->_get_attributes('nodelete')) {
+        $base->log(LA_ERR,'Cannot delete %s/%s: is write protected', $class->type, $id);
+        return;
+    }
+
+    my $sthd = $base->db->prepare_cached(
+        sprintf(
+            q{delete from %s where %s = ?},
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_key_field),
+        )
+    );
+    my $res = $sthd->execute($id);
+    if ($res) {
+        delete($__cache->{$id});
+    }
+
+    $res
+}
+
+sub _rename {
+    my ($class, $base, $id, $newid) = @_;
+
+    my $sthr = $base->db->prepare_cached(
+        sprintf(
+            q{update %s set %s = ? where %s = ?},
+            $base->db->quote_identifier($class->_object_table),
+            $base->db->quote_identifier($class->_key_field),
+            $base->db->quote_identifier($class->_key_field),
+        )
+    );
+
+    if (($sthr->execute($newid, $id) || 0) != 1) {
+        $base->log(LA_ERR, "Erreur renaming %s %s to %s",
+            $class->type,
+            $id, $newid,
+        );
+        return;
+    }
+
+    1;
+}
+
+=head2 db
+
+Return reference to L<DBI> object.
+
+=cut
+
+sub db {
+    return $_[0]->base->db;
+}
+
+sub _quote_object_table {
+    my ($self) = @_;
+    my $table = $self->_object_table or return;
+    $self->db->quote_identifier($table);
+}
+sub _quote_key_field {
+    my ($self) = @_;
+    my $key_field = $self->_key_field or return;
+    $self->db->quote_identifier($key_field);
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+    if ($field eq 'services') {
+        my @services;
+        my $sth = $self->db->prepare_cached(
+            q{ select name from service join service_attributes
+               on okey = ikey
+               where service_attributes.attr = 'dependOn' and value = ?
+               });
+        $sth->execute($self->type . '.' . $self->id);
+        while(my $res = $sth->fetchrow_hashref) {
+            push(@services, $res->{name});
+        }
+        return \@services; 
+    }
+    my $attr = $self->attribute($field) or return;
+    if ($attr->{inline}) {
+    my $sth = $self->db->prepare_cached(
+        sprintf(
+            q{select %s from %s where %s = ?},
+            $self->db->quote_identifier(lc($field)),
+            $self->_quote_object_table,
+            $self->_quote_key_field,
+        )
+    );
+    $sth->execute($self->id);
+    my $res = $sth->fetchrow_hashref or $self->db->rollback;
+    $sth->finish;
+    return $res->{$field};
+    } elsif ($self->_has_extended_attributes) { # else, then we mandatory have extend attr
+        $self->base->{__cache}{"_" . $self->type} ||= {};
+        my $__cache = $self->base->{__cache}{"_" . $self->type};
+        if (!(exists($__cache->{$self->id})
+            && $__cache->{$self->id}{__time} >= time - 1)) {
+        my $sth = $self->db->prepare_cached(
+            sprintf(
+                q{
+                select attr, value from %s 
+                join %s on okey = ikey
+                where %s = ?
+                },
+                $self->db->quote_identifier($self->_object_table_attributes_read),
+                $self->db->quote_identifier($self->_object_table),
+                $self->db->quote_identifier($self->_key_field),
+            )
+        );
+        $sth->execute($self->id);
+        delete($__cache->{$self->id});
+        $__cache->{$self->id}{__time} = time;
+        while(my $res = $sth->fetchrow_hashref) {
+            push(@{$__cache->{$self->id}{$res->{attr}}}, $res->{value});
+        }
+        #return @values > 1 ? \@values : $values[0];
+        }
+        my $val = $__cache->{$self->id}{$field};
+        return @{$val || []} > 1 ? $val : $val->[0];
+    }
+}
+
+sub GetAttributeValue {
+    my ($self, $cfield) = @_;
+
+    my $res = $self->SUPER::GetAttributeValue($cfield) or return;
+
+    my $attribute = $self->attribute($cfield) or do {
+        $self->base->log(LA_WARN, "Unknow attribute $cfield");
+        return;
+    };
+
+    if (my $ref = $attribute->reference) {
+        my @deref;
+        foreach my $v (ref $res ? @{ $res } : $res) {
+            my $derefobj = $self->base->_derefObject($ref, $v);
+            push(@deref, $derefobj->id) if ($derefobj);
+        }
+        return scalar(@deref) > 1 ? \@deref : $deref[0];
+    } else {
+        return $res;
+    }
+}
+
+sub set_fields {
+    my ($self, %data) = @_;
+    my @updated_attributes = ();
+    my @fields;
+    my @vals;
+    my %ext;
+    if (exists($data{services})) {
+        my %old = map { $_ => 0 } $self->get_attributes('services');
+        foreach my $serv (grep { $_ } ref $data{services} ? @{ $data{services} } : $data{services}) {
+            if (!exists($old{$serv})) {
+                my $oserv = $self->base->get_object('service', $serv) or next;
+                $oserv->addAttributeValue('dependOn', $self->type . '.' . $self->id);
+            }
+            $old{$serv} = 1;
+        }
+        foreach my $serv (keys %old) {
+            if (!$old{$serv}) {
+                my $oserv = $self->base->get_object('service', $serv) or next;
+                $oserv->delAttributeValue('dependOn', $self->type . '.' . $self->id);
+            }
+        }
+        delete($data{services});
+    }
+    foreach my $field (keys %data) {
+        my $attr = $self->attribute($field);
+        my $oldval = $self->get_field($field);
+        next if (($data{$field} || '') eq ($oldval || ''));
+        if ($attr->{inline}) {
+        # TODO check fields exists !
+            push(@fields, sprintf("%s = ?", $self->db->quote_identifier($field)));
+            # undef mean unset, handling null for exported:
+            if ($field eq 'exported') {
+                push(@vals, $data{$field} ? 1 : 0);
+            } else {
+                push(@vals, $data{$field} || undef);
+            }
+            push(@updated_attributes, $field);
+        } else {
+            $ext{$field} = $data{$field};
+        }
+    }
+    if (@fields) {
+        my $sth = $self->db->prepare_cached(
+            sprintf(
+                q{update %s set %s where %s = ?},
+                $self->_quote_object_table,
+                join(', ', @fields),
+                $self->_quote_key_field,
+            )
+        );
+        $sth->execute(@vals, $self->id) or do {
+            $self->base->log(LA_ERR,
+                "Cannot update inline field for object %s, %s: %s",
+                $self->type,
+                $self->id,
+                $self->base->db->errstr);
+            return;
+        };
+    }
+    
+    if ($self->_has_extended_attributes) {
+        my $sthd = $self->db->prepare_cached(
+            sprintf(
+                q{delete from %s where okey = ? and attr = ?},
+                $self->db->quote_identifier($self->_object_table_attributes),
+            ),
+        );
+        my $sthd1 = $self->db->prepare_cached(
+            sprintf(
+                q{delete from %s where okey = ? and attr = ? and value = ?},
+                $self->db->quote_identifier($self->_object_table_attributes),
+            ),
+        );
+        my $sthx = $self->db->prepare_cached(
+            sprintf(
+                q{insert into %s (okey, attr, value) values (?,?,?)},
+                $self->db->quote_identifier($self->_object_table_attributes),
+            )
+        );
+        my $sthu = $self->db->prepare_cached(
+            sprintf(
+                q{update %s set value = ? where okey = ? and attr = ?},
+                $self->db->quote_identifier($self->_object_table_attributes),
+            )
+        );
+
+        my $okey = $self->_get_ikey($self->base, $self->id);
+        foreach my $uattr (keys %ext) {
+            my $attr = $self->attribute($uattr);
+            if ($ext{$uattr}) {
+                if ($attr->{multiple}) {
+                    my $updated = 0;
+                    my $oldvalue = $self->get_field($uattr);
+                    my %newvalues = map { $_ => 1 } (ref $ext{$uattr}
+                        ? @{$ext{$uattr}}
+                        : $ext{$uattr});
+                    foreach (grep { $_ } ref $oldvalue ? @{$oldvalue} : $oldvalue) {
+                        if(exists($newvalues{$_})) {
+                            $newvalues{$_} = 0;
+                        } else {
+                            defined($sthd1->execute($okey, $uattr, $_)) or do {
+                                $self->base->log(LA_ERR,
+                                    "Error while updating attributes on %s/%s %s: %s",
+                                    $self->type,
+                                    $self->id,
+                                    $uattr,
+                                    $self->base->db->errstr
+                                );
+                                return;
+                            };
+                            $updated++;
+                        }
+                    }
+                    foreach (grep { $newvalues{$_} } keys %newvalues) {
+                        $sthx->execute($okey, $uattr, $_) or do {
+                            $self->base->log(LA_ERR,
+                                "Error while updating attributes: %s/%s %s: %s",
+                                $self->type,
+                                $self->id,
+                                $uattr,
+                                $self->base->db->errstr
+                            );
+                            return;
+                        };
+                        $updated++;
+                    }
+                    push(@updated_attributes, $uattr) if ($updated);
+                } else {
+                    my $res = $sthu->execute($ext{$uattr}, $okey, $uattr);
+                    defined($res) or do {
+                        $self->base->log(LA_ERR,
+                            "Error while udapting attributes: %s/%s %s: %s",
+                            $self->type,
+                            $self->id,
+                            $uattr,
+                            $self->base->db->errstr
+                        );
+                        return;
+                    };
+                    if ($res == 0) {
+                        $res = $sthx->execute($okey, $uattr, $ext{$uattr});
+                        defined($res) or do {
+                            $self->base->log(LA_ERR,
+                                "Error while updating attributes: %s/%s %s: %s",
+                                $self->type,
+                                $self->id,
+                                $uattr,
+                                $self->base->db->errstr
+                            );
+                            return;
+                        };
+                    }
+                    push(@updated_attributes, $uattr);
+                }
+            } else {
+                defined($sthd->execute($okey, $uattr)) or do {
+                    $self->base->log(LA_ERR,
+                        "Error while deleting attributes: %s/%s %s: %s",
+                        $self->otype,
+                        $self->id,
+                        $uattr,
+                        $self->base->db->errstr
+                    );
+                    return;
+                };
+                push(@updated_attributes, $uattr);
+            }
+        }
+    }
+
+    delete($self->base->{__cache}{"_" . $self->type}{$self->id});
+
+    foreach my $attr (@updated_attributes) {
+        my $oattr = $self->attribute($attr);
+        my $ref = $oattr->reference or next;
+        my $refname = sprintf('%s.%s.%s', $self->type, $self->id, $attr);
+        my $attrref = "oalias=$refname";
+
+        foreach my $alias ($self->base->search_objects($ref, $attrref)) {
+            my $oalias = $self->base->GetAlias($ref, $alias) or next;
+            $oalias->_update_aliases_cache;
+            $oalias->_update_aliases_ptr;
+        }
+    }
+
+    scalar(@updated_attributes);
+}
+
+
+=head2 SetNoDelete($value)
+
+Set nodelete attribute to true or false
+
+=cut
+
+sub SetNoDelete {
+    my ($self, $value) = @_;
+
+    my $sthr = $self->db->prepare_cached(
+        sprintf(
+            q{update %s set nodelete = ? where %s = ?},
+            $self->db->quote_identifier($self->_object_table),
+            $self->db->quote_identifier($self->_key_field),
+        )
+    );
+
+    if (($sthr->execute($value ? 'true' : 'false', $self->id) || 0) != 1) {
+        $self->log(LA_ERR, "Erreur seting nodelete for %s/%s to %s",
+            $self->type,
+            $self->id,
+            $value,
+        );
+        return;
+    }
+
+    1;
+}
+
+=head2 find_next_numeric_id($class, $base, $field, $min, $max)
+
+An optimize version to speedup user/group creation
+
+=cut
+
+sub find_next_numeric_id {
+    my ($class, $base, $field, $min, $max) = @_;
+    $base->attribute($class->type, $field) or return;
+    $min ||=
+        $field eq 'uidNumber' ? 500 :
+        $field eq 'gidNumber' ? 500 :
+        1;
+    $max ||= 65635;
+    $base->log(LA_DEBUG, "Trying to find %s in range %d - %d",
+        $field, $min, $max);
+    my %existsid;
+    $base->temp_switch_unexported(sub {
+        foreach ($class->attributes_summary($base, $field)) {
+            $existsid{ $_ } = 1;
+        }
+    }, 1);
+    $min += 0;
+    $max += 0;
+    for(my $i = $min; $i <= $max; $i++) {
+        $existsid{$i + 0} or do {
+            $base->log(LA_DEBUG, "Next %s found: %d", $field, $i);
+            return $i;
+        };
+    }
+    return;
+}
+
+sub attributes_summary {
+    my ($class, $base, $dotAttribute) = @_;
+
+    my ($attribute, $recursiveAttr) = $dotAttribute =~ /(\w+)(?:\.(.*))?/;
+    
+    my $attr = $base->attribute($class->type, $attribute) or do {
+        $base->log(LA_ERR, "Cannot instantiate %s attribute", $attribute);
+        return;
+    };
+    if (!$attr->readable) {
+        $base->log(LA_WARN, l('Attribute %s is not readable', $attribute));
+        return;
+    }
+    if (!$base->check_acl($class->type, $attribute, 'r')) {
+        $base->log(LA_WARN, l('Permission denied to read attribute %s', $attribute));
+        return;
+    }
+    if ($attr->{managed}) { 
+        return $class->SUPER::attributes_summary($base, $attribute);
+    }
+    my $sth = $base->db->prepare_cached(
+        $attr->{inline}
+            ? sprintf(
+                q{select %s as value from %s where internobject = false} . ($base->{wexported} ? '' : ' and "exported" = true'),
+                $base->db->quote_identifier($attr->iname),
+                $base->db->quote_identifier($class->_object_table),
+            )
+            : sprintf(
+                q{select value from %s join
+                %s on %s.ikey = %s.okey where attr = ? and internobject = false group by value} . 
+                    ($base->{wexported} ? '' : ' and "exported" = true'),
+                $base->db->quote_identifier($class->_object_table),
+                $base->db->quote_identifier($class->_object_table_attributes_read),
+                $base->db->quote_identifier($class->_object_table),
+                $base->db->quote_identifier($class->_object_table_attributes_read),
+            )
+    );
+    $sth->execute($attr->{inline} ? () : ($attr->iname));
+
+    my %values;
+    if ($recursiveAttr) {
+        my $otype = $attr->reference or do {
+            $base->log(LA_ERR, "Cannot do recursive search, no ref for attribute %s", $attribute);
+            return;
+        };
+        my %parentRes = $base->attributes_summary_by_object($otype, $recursiveAttr) or return;
+
+        while (my $res = $sth->fetchrow_hashref) {
+            defined($res->{value}) or next;
+            $values{ $parentRes{ $res->{value} } } = 1;
+        }
+    } else {
+        while (my $res = $sth->fetchrow_hashref) {
+            $values{$res->{value}} = 1 if ($res->{value});
+        }
+    }
+    sort keys %values
+}
+
+=head2 attributes_summary_by_object($base, $attribute)
+
+Return a hash containing object/value peer for C<$attribute>.
+
+=cut
+
+sub attributes_summary_by_object {
+    my ($class, $base, $dotAttribute) = @_;
+
+    my ($attribute, $recursiveAttr) = $dotAttribute =~ /(\w+)(?:\.(.*))?/;
+
+    my $attr = $base->attribute($class->type, $attribute) or do {
+        $base->log(LA_ERR, "Cannot instantiate %s attribute", $attribute);
+        return;
+    };
+    if (!$attr->readable) {
+        $base->log(LA_WARN, l('Attribute %s is not readable', $attribute));
+        return;
+    }
+    if (!$base->check_acl($class->type, $attribute, 'r')) {
+        $base->log(LA_WARN, l('Permission denied to read attribute %s', $attribute));
+        return;
+    }
+
+    if ($attr->{managed}) {
+        return $class->SUPER::attributes_summary_by_object($base, $attribute);
+    }
+    my $sth = $base->db->prepare_cached(
+        $attr->{inline}
+            ? sprintf(
+                q{
+                select name, %s as value from %s} . ($base->{wexported} ? '' : ' where "exported" = true'),
+                $base->db->quote_identifier($attr->iname),
+                $base->db->quote_identifier($class->_object_table),
+            )
+            : sprintf(
+                q{select name, value from %s left join %s on %s.ikey = %s.okey and attr = ?} . ($base->{wexported} ? '' : ' and "exported" = true'),
+                $base->db->quote_identifier($class->_object_table),
+                $base->db->quote_identifier($class->_object_table_attributes_read),
+                $base->db->quote_identifier($class->_object_table),
+                $base->db->quote_identifier($class->_object_table_attributes_read),
+            )
+    );
+    $sth->execute($attr->{inline} ? () : ($attr->iname));
+
+    my %values;
+    if ($recursiveAttr) {
+        my $otype = $attr->reference or do {
+            $base->log(LA_ERR, "Cannot do recursive search, no ref for attribute %s", $attribute);
+            return;
+        };
+        my %parentRes = $base->attributes_summary_by_object($otype, $recursiveAttr) or return;
+
+        while (my $res = $sth->fetchrow_hashref) {
+            defined($res->{value}) or next;
+            push(@{ $values{ $res->{name} } }, @{ $parentRes{ $res->{value} } || []});
+        }
+    } else {
+        while (my $res = $sth->fetchrow_hashref) {
+            defined($res->{value}) or next;
+            push(@{ $values{ $res->{name} } }, $res->{value});
+        }
+    }
+    %values
+}
+
+sub search {
+    my ($class, $base, @filter) = @_;
+
+    # Results groups by attr (OR filter)
+    # foo=1 foo=1 => foo = 1 or foo = 2
+    # foo=1 bar=1 => foo =1 and bar = 2
+    my $results = {};
+    my $noalias = 0;
+
+    @filter = grep { defined($_) && $_ ne '' } @filter;
+    if (!@filter) {
+        my ($package, $filename, $line) = caller;
+        $base->log(LA_DEBUG, "search() call w/o filter at %s:%d", $filename, $line);
+        return $base->list_objects($class->type);
+    }
+
+    while (my $item = shift(@filter)) {
+        # attr=foo => no extra white space !
+        # \W is false, it is possible to have two char
+        my ($attr, $attrref, $operator, $val) = $item =~ /^(\w+)(?:\.([\.\w]+))?(?:([^\w*]+)(.+))?$/ or next;
+        if (!$operator) {
+            $operator = '~';
+            $val = shift(@filter);
+        }
+        my $attribute = $base->attribute($class->type, $attr) or do {
+            $base->log(LA_ERR, "Unknown attribute $attr");
+            return;
+        };
+        $attribute->name eq 'oalias' and $noalias = 1;
+        defined($val) or $val =  '';
+
+        $base->log(LA_DEBUG, "Search for %s %s (ref %s) %s %s", $class->type, $attr, $attrref || '(none)', $operator || '', $val);
+
+        # Invalid filter due to impossible value:
+        if ($operator ne '~' && !($operator eq '=' && ($val eq 'NULL' || $val eq '*'))) {
+            if (!$attribute->checkinputformat($val)) {
+                $base->log(LA_ERR, "Invalid format value $val for attribute $attr");
+                return;
+            }
+        }
+        my $attrKey = $attr;
+
+        if ($attrref) {
+            my $otype = $attribute->reference or do {
+                $base->log(LA_ERR, "Attribute $attr do not refer to another object");
+                return;
+            };
+
+            $attrKey .= '.' . $attrref;
+            my @results = $base->search_objects($otype, "$attrref$operator$val");
+            $base->log(LA_DEBUG, "Sub search %s res: %s", $otype, join(', ', @results));
+
+            if (!@results) {
+                $results->{$attrKey} ||= {};
+                next;
+            }
+
+            ($operator, $val) = ('=', join('||', @results));
+        }
+
+        my @results = $class->_search_uniq_filter($base, $attr, $operator, $val);
+
+        $base->log(LA_DEBUG, "Search result: %s: %s", $attr, join(' ', @results));
+        $results->{$attrKey} ||= {};
+        $results->{$attrKey}{$_} = 1 foreach (@results);
+    }
+
+    # Merging filter result
+
+    my %mresults = map { $_ => 1 } $base->list_objects($class->type);
+
+    foreach my $attr (keys %{ $results }) {
+        my @values = keys %mresults;
+        foreach my $value (sort(@values)) {
+            if (!$results->{$attr}{$value}) {
+                delete($mresults{$value});
+            }
+        }
+    }
+
+    # We add to result aliases pointing to these object:
+    if(scalar(keys %mresults) && !$noalias) {
+        my @alias = $class->_search_uniq_filter($base, 'oalias', '=', join('||', keys %mresults));
+        foreach(@alias) {
+            $mresults{$_} = 1;
+        }
+    }
+
+    return(sort keys %mresults);
+}
+
+sub _search_uniq_filter {
+    my ($class, $base, $attr, $operator, $value) = @_;
+
+    my @attrsql;
+    my @attrbind;
+
+    my $attribute = $base->attribute($class->type, $attr) or do {
+        $base->log(LA_ERR, "Unknown attribute $attr");
+        return;
+    };
+
+    my @values = split(/([\|\&]+)/, $value);
+
+    $base->log(LA_DEBUG, "Uniq search for " . $class->type . "->$attr $operator (%s)", join(' ', @values));
+
+    return unless(@values);
+    
+    # We detect if we can do a very quick search:
+    my $forRef = $operator eq '=' && scalar(@values) > 1 && ! grep { $_ eq '*' or $_ eq  '&&' or $_ eq 'NULL' } @values;
+    if ($forRef) {
+        # Improv perf
+
+        my @oaliases = ();
+        if ($attr ne 'oaliascache' && $attribute->reference) {
+            @oaliases = $base->search_objects($attribute->reference, "oaliascache=$value");
+            $base->log(LA_DEBUG, "Uniq search will match oaliases: %s", join(' ', @oaliases) || '(none)');
+        }
+
+        if ($attribute->{inline}) {
+            my $sql = sprintf(
+                q{select ikey from %s where %s = ANY (?)},
+                $base->db->quote_identifier($class->_object_table),
+                $base->db->quote_identifier($attribute->iname),
+            );
+            push(@attrsql, $sql);
+        } else {
+            my $sql = sprintf(
+                q{select okey from %s where attr = ? and "value" = ANY (?) },
+                    $base->db->quote_identifier($class->_object_table_attributes_read),
+            );
+            push(@attrbind, $attribute->iname);
+            push(@attrsql, $sql);
+        }
+        push(@attrbind, [ @oaliases, grep { $_ ne '||' } @values ]);
+    } else {
+
+    # No optimisation possible:
+    while (defined(my $val = shift(@values))) {
+
+        if ($val eq '&&') {
+            push(@attrsql, 'intersect');
+            next;
+        }
+        if ($val eq '||') {
+            push(@attrsql, 'union');
+            next;
+        }
+
+        $val = $attribute->input($val) unless($operator eq '=' && $val eq 'NULL');
+
+        my $sql;
+
+        # Specific case for unexported attribute, comming from exported value
+        if ($attribute->iname eq 'unexported') {
+            $sql = sprintf(
+                q{select ikey from %s where %s},
+                $base->db->quote_identifier($class->_object_table),
+                $val ? q{exported='f'} : q{exported='t'}
+            )
+        } elsif ($attribute->iname eq 'exported') {
+            $sql = sprintf(
+                q{select ikey from %s where %s},
+                $base->db->quote_identifier($class->_object_table),
+                $val ? q{exported='t'} : q{exported='f'}
+            )
+        } elsif ($attribute->{inline}) {
+            $sql = sprintf(
+                q{select ikey from %s where %s%s %s},
+                $base->db->quote_identifier($class->_object_table),
+                $base->db->quote_identifier($attribute->iname),
+                ($operator eq '~' ? '::text' : ''),
+                $operator eq '=' && $val eq '*'
+                    ? 'is not NULL'
+                    : $operator eq '=' && $val eq 'NULL'
+                    ? 'is NULL'
+                    : $operator eq '~'
+                        ? 'ILIKE ?'
+                        : "$operator ?"
+            );
+            push(@attrbind, $operator eq '~' ? '%' . $val . '%' : $val) unless($operator eq '=' && ($val eq '*' || $val eq 'NULL'));
+        } else {
+            if ($operator eq '=' && $val eq 'NULL') {
+                $sql = sprintf(q{ select ikey from %s where ikey
+                          not in (select okey from %s where attr = ? and ("value" is NOT NULL and value != '')) },
+                          $base->db->quote_identifier($class->_object_table),
+                          $base->db->quote_identifier(
+                              $class->_object_table_attributes_read
+                          ),
+                );
+                push(@attrbind, $attribute->iname);
+            } else {
+                my @oaliases = ();
+                if ($attr ne 'oaliascache' && $attribute->reference) {
+                    @oaliases = $base->search_objects($attribute->reference, "oaliascache=$val");
+                    $base->log(LA_DEBUG, "Uniq search will match oaliases for %s: %s", $val, join(' ', @oaliases) || '(none)');
+                }
+
+                $sql = sprintf(
+                    q{select okey from %s where attr = ? %s},
+                    $base->db->quote_identifier(
+                        $class->_object_table_attributes_read
+                    ),
+                    $val eq '*'
+                        ? ''
+                        : $operator eq '~'
+                            ? q{and value::text ILIKE ANY(?)}
+                            : qq{and value $operator ANY (?)}
+
+                );
+                push(@attrbind, $attribute->iname);
+                my @vals = ( $val, @oaliases );
+                push(@attrbind, $operator eq '~'
+                    ? [ map { '%' . $_ . '%' } @vals ]
+                    : [ @vals ])
+                    unless($val eq '*');
+            }
+        }
+
+        push(@attrsql, $sql);
+    }
+
+    } # Perf
+
+    # building the query
+    if (!$base->{wexported}) {
+        push(@attrsql, 'intersect') if (@attrsql);
+        push(@attrsql, sprintf(
+                q{select ikey from %s where exported = true and internobject = false},
+                $base->db->quote_identifier($class->_object_table)
+            )
+        );
+    }
+    my $sth = $base->db->prepare(
+        sprintf(q{
+            select name from %s
+            where internobject = false
+            %s
+            order by name
+            },
+            $base->db->quote_identifier($class->_object_table),
+            @attrsql
+            ? "and ikey in (\n" . join(" ", @attrsql) . ")\n"
+            : '',
+        )
+    );
+    $sth->execute(@attrbind);
+    my @results;
+    while (my $res = $sth->fetchrow_hashref) {
+        push(@results, $res->{name});
+    }
+    return(@results);
+}
+
+=head2 register_attribute
+
+Register attribute into base
+
+=cut
+
+sub register_attribute {
+    my ($class, $base, $attribute, $comment) = @_;
+
+    $class->is_registered_attribute($base, $attribute) and do {
+        $base->log(LA_ERR, "The attribute $attribute already exists");
+        return;
+    };
+    my $sth = $base->db->prepare(
+        sprintf(q{
+            insert into %s (canonical, description)
+            values (?,?)
+            }, $class->_attributes_table)
+    );
+    my $res = $sth->execute($attribute, $comment);
+}
+
+=head2 is_registered_attribute ($base, $attribute)
+
+Return true is attribute is registered into base
+
+=cut
+
+sub is_registered_attribute {
+    my ($class, $base, $attribute) = @_;
+
+    my $sth = $base->db->prepare(
+        sprintf(q{
+            select 1 from %s where canonical = ?
+            }, $class->_attributes_table
+        )
+    );
+    $sth->execute($attribute);
+    my $res = $sth->fetchrow_hashref;
+    return $res ? 1 : 0;
+}
+
+=head2 list_registered_attributes
+
+Return the list for all registered attribute
+
+=cut
+
+sub list_registered_attributes {
+    my ($class, $base) = @_;
+
+    my $sth = $base->db->prepare(
+        sprintf(q{
+            select canonical from %s
+            }, $class->_attributes_table
+        )
+    );
+    $sth->execute();
+    my @attrs = ();
+
+    while (my $res = $sth->fetchrow_hashref) {
+        push(@attrs, $res->{canonical});
+    }
+    return @attrs;
+}
+
+
+=head2 get_attribute_comment $base, $attribute)
+
+Return comment for C<$attribute>
+
+=cut
+
+# TODO: get_attribute_comment($attr, $base) { $base ||= $self->base ...
+
+sub get_attribute_comment {
+    my ($class, $base, $attribute) = @_;
+    $base->attribute($class->type, $attribute) or do {
+        $base->log(LA_ERR, "The attribute $attribute does not exists");
+        return;
+    };
+    my $sth = $base->db->prepare(
+        sprintf(q{
+            select description from %s
+            where canonical = ?
+            }, $class->_attributes_table)
+    );
+    $sth->execute($attribute);
+    if (my $res = $sth->fetchrow_hashref) {
+        $sth->finish;
+        return $res->{description};
+    } else {
+        return;
+    }
+}
+
+=head2 set_attribute_comment ($base, $attribute, $comment)
+
+Set comment to attribute
+
+=cut
+
+sub set_attribute_comment {
+    my ($class, $base, $attribute, $comment) = @_;
+
+    my $attr = $base->attribute($class->type, $attribute) or do {
+        $base->log(LA_ERR, "The attribute $attribute does not exists");
+        return;
+    };
+    $attr->{inline} and do {
+        $base->log(LA_ERR,
+            "Cannot set comment to inline attribute, sorry, blame the author !"
+        );
+        return;
+    };
+    my $sth = $base->db->prepare(
+        sprintf(q{
+            update %s set description = ?
+            where canonical = ?
+            }, $class->_attributes_table)
+    );
+    my $res = $sth->execute($comment, $attribute);
+}
+
+sub _update_aliases_ptr {
+    my ($self) = @_;
+
+    my $atype = $self->type;
+    my $name  = $self->id;
+    my $base  = $self->base;
+
+    foreach my $otype ($base->list_supported_objects) {
+        foreach my $attr ($base->list_canonical_fields($otype, 'r')) {
+            $attr =~ /^(oalias|modifiedby|createdby)$/ and next;
+            my $attribute = $base->attribute($otype, $attr);
+            my $ref = $attribute->reference or next;
+
+            if ($ref eq $atype) {
+                $base->log(LA_DEBUG, "Searching object referencing alias %s/%s in %s->%s",
+                    $atype, $name, $otype, $attr);
+                foreach my $target ($base->search_objects($otype, "$attr\=$name", 'oalias=NULL')) {
+                    $base->log(LA_DEBUG, "Update ref for object %s/%s", $otype, $target);
+                    my $otarget = $base->get_object($otype, $target) or next;
+                    $otarget->refreshRev;
+                    if ($otype eq 'group' && grep { $_ eq 'dpmt' } $otarget->_get_attributes('sutype')) {
+                        $otarget->_update_employment_manager;
+                    }
+                }
+            }
+        }
+    }
+}
+
+sub _update_aliases_cache {
+    my ($self) = @_;
+
+    my $getoalias = $self->base->db->prepare(
+        sprintf(
+            q{ select * from %s where %s = ? },
+            $self->base->db->quote_identifier($self->_object_table),
+            $self->base->db->quote_identifier($self->_key_field),
+        )
+    );
+    $getoalias->execute($self->id);
+    my $res = $getoalias->fetchrow_hashref or do {
+        $self->base->log(LA_ERR, "Cannot find alias %s / %s", $self->type, $self->id);
+        return;
+    };
+    $getoalias->finish;
+
+    my $ref = $self->base->_derefObject($self->type, $res->{oalias});
+    my $id = $ref ? $ref->id : undef;
+
+    if (($id || '') ne ($res->{oaliascache} || '')) {
+        my $upd = $self->base->db->prepare(
+            sprintf(
+                q{update %s set oaliascache = ? where %s = ?},
+                $self->base->db->quote_identifier($self->_object_table),
+                $self->base->db->quote_identifier($self->_key_field),
+            )
+        );
+        $self->base->log(LA_DEBUG, "Updating Cache for alias %s => %s", $self->id, $id || '(none)');
+        return $upd->execute($id, $self->id);
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Sql>
+
+L<LATMOS::Accounts::Bases::Objects>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/upgrade.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/upgrade.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/upgrade.pm	(revision 2119)
@@ -0,0 +1,1278 @@
+package LATMOS::Accounts::Bases::Sql::upgrade;
+
+package LATMOS::Accounts::Bases::Sql;
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Log;
+
+=head1 NAME
+
+    LATMOS::Accounts::Bases::Sql::upgrade
+
+=head1 DESCRIPTION
+
+Upgrade the SQL schema for SQL Base
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _SqlSchemaUpgrade {
+    my ($self) = @_;
+
+    my $rev = $self->get_global_value('schema_version') || 1;
+
+    my @updates = (
+        {
+            ver => 3,
+            sql => [
+                q{ALTER TABLE nethost_attributes_ips DROP CONSTRAINT
+                nethost_is_single_ip},
+                q{DROP TRIGGER IF EXISTS check_zone_name_unity_tg ON nethost},
+                q{DROP TRIGGER IF EXISTS check_zone_name_unity_tg ON netzone},
+                q{DROP FUNCTION IF EXISTS check_zone_name_unity()},
+                q{ALTER TABLE nethost_attributes_ips DROP CONSTRAINT
+                nethost_is_single_ip},
+                q{INSERT INTO settings(varname, val) VALUES ('schema_version', 3)},
+            ],
+        },
+        {
+            ver => 4,
+            sql => [
+                q{
+                CREATE OR REPLACE FUNCTION nethost_sort_fields()
+                RETURNS trigger AS
+                $BODY$BEGIN
+
+                IF (TG_OP='INSERT') then
+                IF (new.attr='ip') THEN
+                insert into nethost_attributes_ips VALUES (new.*);
+                RETURN NULL;
+                END IF;
+
+                IF (new.attr='macaddr') THEN
+                insert into nethost_attributes_macs VALUES (new.*);
+                RETURN NULL;
+                END IF;
+
+                IF (new.attr='owner') THEN
+                insert into nethost_attributes_users VALUES (new.*);
+                RETURN NULL;
+                END IF;
+
+                IF (new.attr='user') THEN
+                insert into nethost_attributes_users VALUES (new.*);
+                RETURN NULL;
+                END IF;
+                end if;
+
+
+                if (TG_OP='DELETE') THEN
+                RETURN old;
+                else
+                RETURN new;
+                end if;
+                END;$BODY$
+                LANGUAGE plpgsql VOLATILE
+                COST 100;
+                },
+            ],
+        },
+        {
+            ver => 5,
+            sql => [
+                q{
+                CREATE OR REPLACE VIEW address_attributes AS
+                ( ( SELECT address."user" AS value, 'user' AS attr, address.rev AS attr_key, address.ikey AS okey
+                FROM address
+                UNION ALL
+                SELECT address.name AS value, 'name' AS attr, address.rev AS attr_key, address.ikey AS okey
+                FROM address )
+                UNION ALL
+                SELECT address_attributes.value, address_attributes.attr, address_attributes.attr_key, address_attributes.okey
+                FROM address_attributes_base address_attributes )
+                UNION ALL
+                SELECT '1' AS value, 'active' AS attr, address.rev AS attr_key, address.ikey AS okey
+                FROM "user" JOIN address ON "user".name = address."user"
+                WHERE "user".exported AND address.exported and
+                ("user".expire IS NULL or "user".expire > now())
+                }
+            ],
+        },
+        {
+            ver => 6,
+            sql => [
+                q{
+                CREATE TABLE request
+                (
+                id serial NOT NULL,
+                "create" timestamp with time zone NOT NULL DEFAULT now(),
+                name text NOT NULL,
+                object text,
+                apply timestamp with time zone NOT NULL DEFAULT now(),
+                done timestamp with time zone,
+                "user" text,
+                CONSTRAINT request_pkey PRIMARY KEY (id ),
+                CONSTRAINT request_name_fkey FOREIGN KEY (name)
+                REFERENCES accreq (name) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE,
+                CONSTRAINT request_user_fkey FOREIGN KEY ("user")
+                REFERENCES "user" (name) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE SET NULL
+                )
+                },
+                q{
+                CREATE INDEX fki_request_name_fkey ON request
+                USING btree (name )
+                },
+                q{
+                CREATE INDEX fki_request_user_fkey ON request
+                USING btree ("user" )
+                },
+                q{
+                CREATE TABLE request_attributes
+                (
+                reqid bigint,
+                attribute text NOT NULL,
+                value text,
+                CONSTRAINT request_id_fkey FOREIGN KEY (reqid)
+                REFERENCES request (id) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE
+                )
+                },
+                q{
+                CREATE INDEX fki_request_id_fkey ON request_attributes
+                USING btree (reqid )
+                },
+                q{
+                delete from accreq_attributes_list
+                }
+            ],
+        },
+        {
+            ver => 7,
+            sql => [
+                q{
+                ALTER TABLE request ADD COLUMN automated boolean NOT NULL DEFAULT false;
+                },
+                q{
+                ALTER TABLE request ADD COLUMN objrev bigint;
+                },
+                q{
+                ALTER TABLE aliases ADD COLUMN description text;
+                },
+                q{
+                ALTER TABLE revaliases ADD COLUMN description text;
+                },
+                q{
+                CREATE OR REPLACE FUNCTION group_sort_fields()
+                RETURNS trigger AS
+                $BODY$BEGIN
+
+                IF (TG_OP='INSERT') then
+                IF (new.attr='managedBy'
+                OR  new.attr='managedAlsoBy'
+                OR  new.attr='member'
+                OR  new.attr='memberUID') THEN
+                insert into group_attributes_users VALUES (new.*);
+                RETURN NULL;
+                END IF;
+
+                IF (new.attr='sutype') THEN
+                insert into group_attributes_sutypes VALUES (new.*);
+                RETURN NULL;
+                END IF;
+                end if;
+
+
+                IF (TG_OP = 'UPDATE') THEN
+                IF (new.attr='gidNumber') then
+                update "group" set gidnumber = new.value::integer where
+                "group".ikey = new.okey;
+                RETURN NULL;
+                END IF;
+                END IF;
+
+                IF (TG_OP = 'DELETE') THEN
+                IF (old.attr='exported') then
+                update "group" set exported = false where "group".ikey = old.okey;
+                return null;
+                end if;
+                else
+                IF (new.attr='exported') then
+                update "group" set exported = true where "group".ikey = new.okey;
+                RETURN NULL;
+                end if;
+                END IF;
+
+                if (TG_OP='DELETE') THEN
+                RETURN old;
+                else
+                RETURN new;
+                end if;
+                END;$BODY$
+                LANGUAGE plpgsql VOLATILE
+                COST 100;
+                },
+            ],
+        },
+        {
+            ver => 8,
+            sql => [
+                q{
+                CREATE TABLE objectslogs
+                (
+                logkey SERIAL NOT NULL,
+                ikey bigint NOT NULL,
+                irev bigint,
+                otype text NOT NULL,
+                name text NOT NULL,
+                changetype text NOT NULL,
+                username text NOT NULL,
+                message text NOT NULL,
+                logdate timestamp with time zone NOT NULL DEFAULT now(),
+                CONSTRAINT objectlogs_pkey PRIMARY KEY (logkey)
+                );},
+                q{
+                CREATE INDEX objectlogs_name_idx
+                ON objectslogs
+                USING btree
+                (name);
+                },
+                q{
+                CREATE INDEX objectslogs_ikey_idx
+                ON objectslogs
+                USING btree
+                (ikey);
+                },
+                q{
+                CREATE INDEX objectslogs_otype_idx
+                ON objectslogs
+                USING btree
+                (otype);
+                }
+            ],
+        },
+        {
+            ver => 9,
+            sql => [
+                q{
+                CREATE TABLE nethost_attributes_nethosts
+                (
+                CONSTRAINT nethost_attributes_nethosts_pkey PRIMARY KEY (attr_key),
+                CONSTRAINT nethost_attr_nethosts_fkey FOREIGN KEY (okey)
+                REFERENCES nethost (ikey) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE,
+                CONSTRAINT nethost_attr_nethosts_nethosts FOREIGN KEY ("value")
+                REFERENCES nethost (name) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE
+                )
+                INHERITS (nethost_attributes);
+
+                CREATE INDEX fki_nethost_attr_nethosts_fkey
+                ON nethost_attributes_nethosts
+                USING btree
+                (okey);
+
+                CREATE TRIGGER nethost_attr_nethosts_update
+                AFTER INSERT OR UPDATE OR DELETE
+                ON nethost_attributes_nethosts
+                FOR EACH ROW
+                EXECUTE PROCEDURE nethost_attr_update_ref();
+
+
+                CREATE OR REPLACE FUNCTION nethost_sort_fields()
+                RETURNS trigger AS
+                $BODY$BEGIN
+
+                IF (TG_OP='INSERT') then
+                IF (new.attr='ip') THEN
+                insert into nethost_attributes_ips VALUES (new.*);
+                RETURN NULL;
+                END IF;
+
+                IF (new.attr='macaddr') THEN
+                insert into nethost_attributes_macs VALUES (new.*);
+                RETURN NULL;
+                END IF;
+
+                IF (new.attr='owner') THEN
+                insert into nethost_attributes_users VALUES (new.*);
+                RETURN NULL;
+                END IF;
+
+                IF (new.attr='user') THEN
+                insert into nethost_attributes_users VALUES (new.*);
+                RETURN NULL;
+                END IF;
+
+                IF (new.attr='related') THEN
+                insert into nethost_attributes_nethosts VALUES (new.*);
+                RETURN NULL;
+                END IF;
+                end if;
+
+                if (TG_OP='DELETE') THEN
+                RETURN old;
+                else
+                RETURN new;
+                end if;
+                END;$BODY$
+                LANGUAGE plpgsql VOLATILE
+                COST 100;
+
+                }
+            ],
+        },
+        {
+            ver => 10,
+            sql => [
+                q{
+                CREATE OR REPLACE FUNCTION service_attr_update_ref()
+                RETURNS trigger AS
+                $BODY$begin
+
+                IF (TG_OP != 'INSERT') then
+                update "service" set date = now() where "service".ikey = old.okey;
+                end if;
+                IF (TG_OP != 'DELETE') then
+                update "service"  set date = now() where "service".ikey = new.okey;
+                end if;
+
+                IF (TG_OP = 'DELETE') then
+                return old;
+                ELSE
+                return new;
+                END IF;
+
+                END;$BODY$
+                LANGUAGE plpgsql VOLATILE
+                COST 100;
+                },
+                q{
+
+                CREATE TABLE service
+                (
+                "start" date,
+                "end" date,
+                CONSTRAINT service_pkey PRIMARY KEY (name),
+                CONSTRAINT service_ikey_uniq UNIQUE (ikey)
+                )
+                INHERITS (objects);
+                },
+                q{
+                CREATE TABLE service_attributes_list
+                (
+                ikey integer NOT NULL DEFAULT nextval('ikey_seq'::regclass),
+                canonical text NOT NULL,
+                description text,
+                CONSTRAINT service_attributes_list_pkey PRIMARY KEY (ikey),
+                CONSTRAINT g_attr_l_service_uniq UNIQUE (canonical)
+                )
+                INHERITS (revisions, attributes_list)
+                WITH (
+                OIDS=FALSE
+                );
+
+                CREATE TABLE service_attributes
+                (
+                CONSTRAINT service_attributes_pkey PRIMARY KEY (attr_key),
+                CONSTRAINT service_attr_users_okey_fkey FOREIGN KEY (okey)
+                REFERENCES service (ikey) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE,
+                CONSTRAINT service_attributes_attr_fkey FOREIGN KEY (attr)
+                REFERENCES service_attributes_list (canonical) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE NO ACTION
+                )
+                INHERITS (attributes)
+                WITH (
+                OIDS=FALSE
+                );
+
+                CREATE INDEX fki_service_attributes_attr_fkey
+                ON nethost_attributes
+                USING btree
+                (attr);
+
+                CREATE INDEX service_attr_value_idx
+                ON nethost_attributes
+                USING btree
+                (value);
+
+                CREATE TRIGGER serivce_attr_update
+                AFTER INSERT OR UPDATE OR DELETE
+                ON service_attributes
+                FOR EACH ROW
+                EXECUTE PROCEDURE service_attr_update_ref();
+                },
+                q{
+                CREATE TABLE service_attributes_users
+                (
+                CONSTRAINT service_attributes_user_pkey PRIMARY KEY (attr_key),
+                CONSTRAINT service_attr_users_okey_fkey FOREIGN KEY (okey)
+                REFERENCES service (ikey) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE,
+                CONSTRAINT service_attributes_users_attr_fkey FOREIGN KEY (attr)
+                REFERENCES service_attributes_list (canonical) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE NO ACTION,
+                CONSTRAINT service_attributes_users_user_fkey FOREIGN KEY (value)
+                REFERENCES "user" (name) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE,
+                CONSTRAINT service_attributes_users_uniq UNIQUE (okey, attr, value)
+                )
+                INHERITS (service_attributes);
+
+                CREATE INDEX fki_service_attr_users_okey_fkey
+                ON service_attributes_users
+                USING btree
+                (okey);
+
+                CREATE INDEX fki_service_attributes_users_attr_fkey
+                ON service_attributes_users
+                USING btree
+                (attr);
+
+                CREATE INDEX fki_service_attributes_users_user_fkey
+                ON service_attributes_users
+                USING btree
+                (value);
+
+                CREATE TRIGGER service_attributes_users_update_ref
+                AFTER INSERT OR UPDATE OR DELETE
+                ON service_attributes_users
+                FOR EACH ROW
+                EXECUTE PROCEDURE service_attr_update_ref();
+
+                CREATE OR REPLACE FUNCTION service_sort_fields()
+                RETURNS trigger AS
+                $BODY$BEGIN
+
+                if (TG_OP='INSERT' or TG_OP='UPDATE') THEN
+                IF (new.attr='manager') THEN
+                insert into service_attributes_users VALUES (new.*);
+                RETURN NULL;
+                END IF;
+                END IF;
+
+                if (TG_OP='DELETE') THEN
+                RETURN old;
+                else
+                RETURN new;
+                end if;
+                END;$BODY$
+                LANGUAGE plpgsql VOLATILE
+                COST 100;
+
+                CREATE TRIGGER service_sort_field_tg
+                BEFORE INSERT OR UPDATE OR DELETE
+                ON service_attributes
+                FOR EACH ROW
+                EXECUTE PROCEDURE service_sort_fields();
+
+                CREATE TRIGGER service_rev_tg
+                BEFORE INSERT OR UPDATE OR DELETE
+                ON service
+                FOR EACH ROW
+                EXECUTE PROCEDURE rev_tg_f();
+                },
+            ],
+        },
+        {
+            ver => 11,
+            sql => [
+                q{
+                ALTER TABLE "user" ADD COLUMN endcircuit timestamp with time zone
+                }
+            ],
+        },
+        {
+            ver => 12,
+            sql => [
+                q{
+                CREATE OR REPLACE VIEW user_attributes AS
+                (        (        (        (        (        (        (        (        (        (        (
+                SELECT "user".uidnumber::text AS value, 'uidnumber' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM "user"
+                UNION ALL
+                SELECT "user".gidnumber::text AS value, 'gidnumber' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM "user")
+                UNION ALL
+                SELECT "user".name AS value, 'name' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM "user")
+                UNION ALL
+                SELECT "user".expire::text AS value, 'expire' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM "user"
+                WHERE "user".expire IS NOT NULL)
+                UNION ALL
+                SELECT '1'::text AS value, 'exported' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                WHERE "user".exported = true)
+                UNION ALL
+                SELECT address_attributes_site.value, 'allsite' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                JOIN address ON address."user" = "user".name
+                JOIN address_attributes_site ON address_attributes_site.okey = address.ikey
+                WHERE address_attributes_site.attr = 'site'::text AND address.exported = true)
+                UNION ALL
+                SELECT "group".name AS value, 'memberOf' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                JOIN group_attributes ON group_attributes.value = "user".name
+                JOIN "group" ON group_attributes.okey = "group".ikey
+                WHERE group_attributes.attr = 'memberUID'::text)
+                UNION ALL
+                SELECT user_attributes.value, user_attributes.attr, user_attributes.attr_key, user_attributes.okey
+                FROM user_attributes_base user_attributes)
+                UNION ALL
+                SELECT "group".name AS value, 'departments' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "group"
+                JOIN group_attributes_sutypes ON "group".ikey = group_attributes_sutypes.okey
+                JOIN group_attributes_users ON "group".ikey = group_attributes_users.okey AND group_attributes_users.attr = 'memberUID'::text
+                JOIN "user" ON "user".name = group_attributes_users.value
+                WHERE group_attributes_sutypes.value = 'dpmt'::text AND group_attributes_sutypes.attr = 'sutype'::text)
+                UNION ALL
+                SELECT "group".name AS value, 'cells' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "group"
+                JOIN group_attributes_sutypes ON "group".ikey = group_attributes_sutypes.okey
+                JOIN group_attributes_users ON "group".ikey = group_attributes_users.okey AND group_attributes_users.attr = 'memberUID'::text
+                JOIN "user" ON "user".name = group_attributes_users.value
+                WHERE group_attributes_sutypes.value = 'cell'::text AND group_attributes_sutypes.attr = 'sutype'::text)
+                UNION ALL
+                SELECT "group".name AS value, 'managedObjects' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                JOIN group_attributes ON group_attributes.value = "user".name
+                JOIN "group" ON group_attributes.okey = "group".ikey
+                WHERE group_attributes.attr = 'managedBy'::text)
+                UNION ALL
+                SELECT justify_interval(now() - "user".expire)::text AS value, 'expired' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                WHERE "user".expire <= now())
+                UNION ALL
+                SELECT 1::text AS value, 'active' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                WHERE ("user".expire IS NULL OR "user".expire >= now()) AND "user".exported = true
+                UNION ALL
+                SELECT 0::text AS value, 'active' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                WHERE ("user".expire IS NOT NULL and "user".expire <= now()) or "user".exported = false;
+                },
+                q{
+                CREATE OR REPLACE FUNCTION rev_tg_aliases_f()
+                RETURNS trigger AS
+                $BODY$begin
+
+                IF (TG_OP = 'DELETE') THEN
+                update "user" set rev = nextval('revisions_rev_seq'::regclass), "date" = now() where "name" = ANY (old.forward);
+                return old;
+                ELSE
+                IF (TG_OP = 'UPDATE') THEN
+                update "user" set rev = nextval('revisions_rev_seq'::regclass), "date" = now() where "name" = ANY (old.forward);
+                END IF;
+                update "user" set rev = nextval('revisions_rev_seq'::regclass), "date" = now() where "name" = ANY (new.forward);
+                return new;
+                END IF;
+
+                end;$BODY$
+                LANGUAGE plpgsql VOLATILE
+                COST 100;
+
+                CREATE TRIGGER aliases_user_tg
+                AFTER INSERT OR UPDATE OR DELETE
+                ON aliases
+                FOR EACH ROW
+                EXECUTE PROCEDURE rev_tg_aliases_f();
+                },
+            ],
+        },
+        {
+            ver => 13,
+            sql => [
+                q{
+                CREATE TABLE "stat"
+                (
+                name text NOT NULL,
+                CONSTRAINT stat_pkey PRIMARY KEY (name),
+                CONSTRAINT stat_okey_uniq UNIQUE (ikey)
+                )
+                INHERITS (objects);
+                CREATE INDEX stat_exported_idx
+                ON stat USING btree (exported);
+                CREATE UNIQUE INDEX stat_rev_idx
+                ON stat USING btree (rev);
+
+                CREATE OR REPLACE FUNCTION stat_attr_update_ref()
+                RETURNS trigger AS
+                $BODY$begin
+
+                IF (TG_OP != 'INSERT') then
+                update "stat" set date = now() where "stat".ikey = old.okey;
+                end if;
+                IF (TG_OP != 'DELETE') then
+                update "stat"  set date = now() where "stat".ikey = new.okey;
+                end if;
+
+                IF (TG_OP = 'DELETE') then
+                return old;
+                ELSE
+                return new;
+                END IF;
+
+                END;$BODY$
+                LANGUAGE plpgsql VOLATILE
+                COST 100;
+                },
+                q{
+                CREATE TABLE stat_attributes
+                (
+                CONSTRAINT stat_attributes_pkey PRIMARY KEY (attr_key),
+                CONSTRAINT stat FOREIGN KEY (okey)
+                REFERENCES stat (ikey) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE
+                )
+                INHERITS (attributes);
+                CREATE INDEX stat_attributes_attr_fkey
+                ON stat_attributes
+                USING btree
+                (attr);
+                CREATE INDEX stat_attr_value_idx
+                ON stat_attributes
+                USING btree
+                (value);
+                CREATE TRIGGER stat_attr_update
+                AFTER INSERT OR UPDATE OR DELETE
+                ON stat_attributes
+                FOR EACH ROW
+                EXECUTE PROCEDURE stat_attr_update_ref();
+                },
+                q{
+                CREATE TABLE stat_attributes_list
+                (
+                CONSTRAINT stat_attr_list_pkey PRIMARY KEY (ikey),
+                CONSTRAINT stat_attr_l_c_uniq UNIQUE (canonical)
+                )
+                INHERITS (revisions, attributes_list);
+                },
+                q{
+                CREATE TABLE statsentry
+                (
+                okey integer NOT NULL,
+                id bigint NOT NULL DEFAULT nextval('revisions_rev_seq'::regclass),
+                tstamp timestamp with time zone NOT NULL DEFAULT now(),
+                CONSTRAINT stat FOREIGN KEY (okey)
+                REFERENCES stat (ikey) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE,
+                CONSTRAINT statsentry_pkey PRIMARY KEY (id)
+                );
+                CREATE TABLE statvalues
+                (
+                sid bigint NOT NULL,
+                value text NOT NULL,
+                count bigint NOT NULL,
+                CONSTRAINT statvalues_pkey PRIMARY KEY (sid, value),
+                CONSTRAINT statvalues_fkey FOREIGN KEY (sid)
+                REFERENCES statsentry (id) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE
+                )
+                },
+            ],
+        },
+        {
+            ver => 14,
+            sql => [
+                q{
+                ALTER TABLE objects ADD COLUMN createdby text;
+                },
+                q{
+                ALTER TABLE objects ADD COLUMN modifiedby text;
+                },
+            ],
+        },
+        {
+            ver => 15,
+            sql => [
+                q{
+                CREATE TABLE "employment"
+                (
+                name text NOT NULL,
+                "user" text NOT NULL,
+                firstday date NOT NULL default now(),
+                lastday  date,
+                CONSTRAINT employment_pkey PRIMARY KEY (name),
+                CONSTRAINT employment_okey_uniq UNIQUE (ikey),
+                CONSTRAINT employment_user_fkey FOREIGN KEY ("user")
+                REFERENCES "user" (name) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE
+                )
+                INHERITS (objects);
+                CREATE INDEX employment_exported_idx
+                ON employment USING btree (exported);
+                CREATE UNIQUE INDEX employment_rev_idx
+                ON employment USING btree (rev);
+
+                CREATE OR REPLACE FUNCTION employment_attr_update_ref()
+                RETURNS trigger AS
+                $BODY$begin
+
+                IF (TG_OP != 'INSERT') then
+                update "employment" set date = now() where "employment".ikey = old.okey;
+                end if;
+                IF (TG_OP != 'DELETE') then
+                update "employment"  set date = now() where "employment".ikey = new.okey;
+                end if;
+
+                IF (TG_OP = 'DELETE') then
+                return old;
+                ELSE
+                return new;
+                END IF;
+
+                END;$BODY$
+                LANGUAGE plpgsql VOLATILE
+                COST 100;
+                },
+                q{
+                CREATE TABLE employment_attributes
+                (
+                CONSTRAINT employment_attributes_pkey PRIMARY KEY (attr_key),
+                CONSTRAINT employment FOREIGN KEY (okey)
+                REFERENCES employment (ikey) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE
+                )
+                INHERITS (attributes);
+                CREATE INDEX employment_attributes_attr_fkey
+                ON employment_attributes
+                USING btree
+                (attr);
+                CREATE INDEX employment_attr_value_idx
+                ON employment_attributes
+                USING btree
+                (value);
+                CREATE TRIGGER employment_attr_update
+                AFTER INSERT OR UPDATE OR DELETE
+                ON employment_attributes
+                FOR EACH ROW
+                EXECUTE PROCEDURE employment_attr_update_ref();
+                },
+                q{
+                CREATE TABLE employment_attributes_list
+                (
+                CONSTRAINT employment_attr_list_pkey PRIMARY KEY (ikey),
+                CONSTRAINT employment_attr_l_c_uniq UNIQUE (canonical)
+                )
+                INHERITS (revisions, attributes_list);
+                },
+                q{
+                CREATE INDEX employment_firstday_idx
+                ON employment
+                USING btree
+                (firstday NULLS FIRST);
+                CREATE INDEX employment_lastday_idx
+                ON employment
+                USING btree
+                (lastday NULLS LAST);
+                },
+            ],
+        },
+        {
+            ver => 16,
+            sql => [
+                q{
+                ALTER TABLE aliases
+                ADD CONSTRAINT aliases_ikey_uniq UNIQUE(ikey);
+                CREATE OR REPLACE FUNCTION aliases_attr_update_ref()
+                RETURNS trigger AS
+                $BODY$begin
+
+                IF (TG_OP != 'INSERT') then
+                update "aliases" set date = now() where "aliases".ikey = old.okey;
+                end if;
+                IF (TG_OP != 'DELETE') then
+                update "aliases"  set date = now() where "aliases".ikey = new.okey;
+                end if;
+
+                IF (TG_OP = 'DELETE') then
+                return old;
+                ELSE
+                return new;
+                END IF;
+
+                END;$BODY$
+                LANGUAGE plpgsql VOLATILE
+                COST 100;
+                },
+                q{
+                CREATE TABLE aliases_attributes_base
+                (
+                CONSTRAINT aliases_attributes_base_pkey PRIMARY KEY (attr_key),
+                CONSTRAINT aliases_fkey FOREIGN KEY (okey)
+                REFERENCES aliases (ikey) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE
+                )
+                INHERITS (attributes);
+                CREATE INDEX aliases_attributes_base_attr_fkey
+                ON aliases_attributes_base
+                USING btree
+                (attr);
+                CREATE INDEX aliases_attr_value_idx
+                ON aliases_attributes_base
+                USING btree
+                (value);
+                CREATE TRIGGER aliases_attr_update
+                AFTER INSERT OR UPDATE OR DELETE
+                ON aliases_attributes_base
+                FOR EACH ROW
+                EXECUTE PROCEDURE aliases_attr_update_ref();
+                },
+                q{
+                CREATE OR REPLACE VIEW aliases_attributes AS
+                SELECT "user".name AS value,
+                'user'::text AS attr,
+                aliases.rev AS attr_key,
+                aliases.ikey AS okey
+                FROM aliases
+                JOIN "user" ON aliases.name = "user".name OR aliases.forward = ARRAY["user".name]
+                union all
+                select value, attr, attr_key, okey from aliases_attributes_base
+                UNION ALL
+                SELECT "aliases".name AS value,
+                'name'::text AS attr,
+                "aliases".rev AS attr_key,
+                "aliases".ikey AS okey
+                FROM "aliases";
+
+                CREATE OR REPLACE RULE aliases_attributes_insert AS
+                ON INSERT TO aliases_attributes DO INSTEAD  INSERT INTO aliases_attributes_base (value, attr, okey)
+                VALUES (new.value, new.attr, new.okey);
+
+                CREATE OR REPLACE RULE aliases_attributes_update AS
+                ON UPDATE TO aliases_attributes DO INSTEAD  UPDATE aliases_attributes_base SET value = new.value, attr = new.attr, okey = new.okey
+                WHERE aliases_attributes_base.attr_key = old.attr_key AND aliases_attributes_base.attr = old.attr;
+
+                CREATE OR REPLACE RULE aliases_atttributes_delete AS
+                ON DELETE TO aliases_attributes DO INSTEAD  DELETE FROM aliases_attributes_base
+                WHERE aliases_attributes_base.attr_key = old.attr_key AND aliases_attributes_base.attr = old.attr;
+                }
+
+            ],
+        },
+        {
+            ver => 17,
+            sql => [
+                q{
+                CREATE TABLE passwordreset
+                (
+                -- HÃ©ritÃ©(e) from table revisions:  rev integer NOT NULL DEFAULT nextval('revisions_rev_seq'::regclass),
+                -- HÃ©ritÃ©(e) from table revisions:  date timestamp with time zone NOT NULL DEFAULT now(),
+                -- HÃ©ritÃ©(e) from table revisions:  "create" timestamp with time zone NOT NULL DEFAULT now(),
+                -- HÃ©ritÃ©(e) from table revisions:  ikey integer NOT NULL DEFAULT nextval('ikey_seq'::regclass),
+                "user" text NOT NULL,
+                id text NOT NULL,
+                CONSTRAINT passwordreset_pkey PRIMARY KEY (id),
+                CONSTRAINT password_reset_user_fkey FOREIGN KEY ("user")
+                REFERENCES "user" (name) MATCH SIMPLE
+                ON UPDATE CASCADE ON DELETE CASCADE
+                )
+                INHERITS (revisions)
+                WITH (
+                OIDS=FALSE
+                );
+                },
+            ],
+        },
+        {
+            ver => 18,
+            sql => [
+                q{
+                ALTER TABLE employment
+                ADD CHECK (lastday is null or firstday <= lastday);
+                }
+            ],
+        },
+        {
+            ver => 19,
+            sql => [
+                q{
+                ALTER TABLE "nethost" ADD COLUMN expire timestamp with time zone;
+                }
+            ],
+        },
+        {
+            ver => 20,
+            sql => [
+                q{
+                CREATE OR REPLACE VIEW user_attributes AS
+                SELECT "user".uidnumber::text AS value, 'uidnumber' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                UNION ALL
+                SELECT "user".gidnumber::text AS value, 'gidnumber' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                UNION ALL
+                SELECT "user".name AS value, 'name' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                UNION ALL
+                SELECT "user".expire::text AS value, 'expire' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                WHERE "user".expire IS NOT NULL
+                UNION ALL
+                SELECT '1'::text AS value, 'exported' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                WHERE "user".exported = true
+                UNION ALL
+                SELECT address_attributes_site.value, 'allsite' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                JOIN address ON address."user" = "user".name
+                JOIN address_attributes_site ON address_attributes_site.okey = address.ikey
+                WHERE address_attributes_site.attr = 'site'::text AND address.exported = true
+                UNION ALL
+                SELECT "group".name AS value, 'memberOf' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                JOIN group_attributes ON group_attributes.value = "user".name
+                JOIN "group" ON group_attributes.okey = "group".ikey
+                WHERE group_attributes.attr = 'memberUID'::text
+                UNION ALL
+                SELECT user_attributes.value, user_attributes.attr, user_attributes.attr_key, user_attributes.okey
+                FROM user_attributes_base user_attributes
+                UNION ALL
+                SELECT "group".name AS value, 'departments' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "group"
+                JOIN group_attributes_sutypes ON "group".ikey = group_attributes_sutypes.okey
+                JOIN group_attributes_users ON "group".ikey = group_attributes_users.okey AND group_attributes_users.attr = 'memberUID'::text
+                JOIN "user" ON "user".name = group_attributes_users.value
+                WHERE group_attributes_sutypes.value = 'dpmt'::text AND group_attributes_sutypes.attr = 'sutype'::text
+                UNION ALL
+                SELECT "group".name AS value, 'cells' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "group"
+                JOIN group_attributes_sutypes ON "group".ikey = group_attributes_sutypes.okey
+                JOIN group_attributes_users ON "group".ikey = group_attributes_users.okey AND group_attributes_users.attr = 'memberUID'::text
+                JOIN "user" ON "user".name = group_attributes_users.value
+                WHERE group_attributes_sutypes.value = 'cell'::text AND group_attributes_sutypes.attr = 'sutype'::text
+                UNION ALL
+                SELECT "group".name AS value, 'managedObjects' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                JOIN group_attributes ON group_attributes.value = "user".name
+                JOIN "group" ON group_attributes.okey = "group".ikey
+                WHERE group_attributes.attr = 'managedBy'::text
+                UNION ALL
+                SELECT justify_interval(now() - "user".expire)::text AS value, 'expired' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                WHERE "user".expire <= now()
+                UNION ALL
+                SELECT 1::text AS value, 'active' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                WHERE ("user".expire IS NULL OR "user".expire >= now()) AND "user".exported = true
+                UNION ALL
+                SELECT 0::text AS value, 'active' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                WHERE "user".expire IS NOT NULL AND "user".expire <= now() OR "user".exported = false
+                UNION ALL
+                SELECT CASE WHEN "user".exported = false THEN 'unexported'
+                WHEN "user".expire IS NOT NULL AND "user".expire <= now() THEN 'expired'
+                ELSE 'active' END AS value, 'status' AS attr, "user".rev AS attr_key, "user".ikey AS okey
+                FROM "user"
+                ;
+
+                }
+            ],
+        },
+        {
+            ver => 21,
+            sql => [
+                'ALTER TABLE objects ADD COLUMN oalias text',
+                'ALTER TABLE objects
+                ADD COLUMN nodelete boolean NOT NULL DEFAULT false',
+                'ALTER TABLE objects
+                ADD COLUMN internobject boolean NOT NULL DEFAULT false',
+            ],
+        },
+        {
+            ver => 22,
+            sql => [
+                'CREATE INDEX fki_employment
+                ON employment_attributes
+                USING btree (okey);'
+            ],
+        },
+        {
+            ver => 23,
+            sql => [
+                'ALTER TABLE address ADD COLUMN expire timestamp with time zone'
+            ],
+        },
+        {
+            ver => 24,
+            sql => [
+                    q{
+                    CREATE OR REPLACE FUNCTION user_sort_fields()
+                    RETURNS trigger AS
+                    $BODY$DECLARE
+                    rec RECORD;
+                    BEGIN
+
+                    IF (TG_OP='UPDATE' or TG_OP='INSERT') THEN
+
+                    IF (new.attr='locked') THEN
+                    IF (TG_OP = 'INSERT') THEN
+                    new.value = now()::text;
+                    ELSIF (TG_OP='UPDATE') THEN
+                    new.value = old.value;
+                    END IF;
+                    END IF;
+
+                    IF (new.attr='nickname') THEN
+                    PERFORM 1 from "user" where "user".ikey = new.okey
+                        and new.value = "user".name;
+                    IF NOT FOUND THEN
+                    select aliases.name into rec from aliases join "user"
+                    on array["user".name] = aliases.forward
+                    where "user".ikey = new.okey and aliases.name = new.value;
+                    IF NOT FOUND THEN
+                    insert into aliases ("name", "forward", "description", exported)
+                    select new.value, array["user".name], 'Forward for ' || "user".name || ' nickname', "user".exported from "user" where "user".ikey = new.okey;
+                    END IF;
+                    END IF;
+                    END IF;
+
+                    IF (new.attr='uidNumber') THEN
+                    update "user" set uidnumber = new.value::integer where ikey = new.okey;
+                    RETURN NULL;
+                    END IF;
+
+                    IF (new.attr='gidNumber') THEN
+                    update "user" set gidnumber = new.value::integer where ikey = new.okey;
+                    RETURN null;
+                    END IF;
+
+                    IF (new.attr='expire') THEN
+                    update "user" set expire = new.value::timestamp where ikey = new.okey;
+                    RETURN NULL;
+                    END IF;
+
+                    IF (new.attr='exported') THEN
+                    update "user" set exported = true where ikey = new.okey;
+                    RETURN NULL;
+                    END IF;
+
+                    END IF;
+
+                    IF (TG_OP = 'DELETE') THEN
+
+                    IF (old.attr='expire') THEN
+                    update "user" set expire = NULL where ikey = old.okey;
+                    RETURN NULL;
+                    END IF;
+
+                    IF (old.attr='exported') THEN
+                    update "user" set exported = false where ikey = old.okey;
+                    RETURN NULL;
+                    END IF;
+
+                    END IF;
+
+
+                    IF (TG_OP='INSERT') THEN
+                    IF (new.attr='manager') THEN
+                    insert into user_attributes_users VALUES (new.*);
+                    RETURN NULL;
+                    END IF;
+
+                    IF (new.attr='site') THEN
+                    insert into user_attributes_site VALUES (new.*);
+                    RETURN NULL;
+                    END IF;
+
+                    IF (new.attr='department') THEN
+                    insert into user_attributes_groups VALUES (new.*);
+                    RETURN NULL;
+                    END IF;
+
+                    IF (new.attr='contratType') THEN
+                    insert into user_attributes_groups VALUES (new.*);
+                    RETURN NULL;
+                    END IF;
+
+                    IF (new.attr='jobType') THEN
+                    insert into user_attributes_groups VALUES (new.*);
+                    RETURN NULL;
+                    END IF;
+
+                    END IF;
+
+                    IF (TG_OP='DELETE') then
+                    RETURN old;
+                    ELSE
+                    RETURN new;
+                    end if;
+                    END;$BODY$
+                    LANGUAGE plpgsql VOLATILE
+                    COST 100;
+                },
+            ],
+        },
+        {
+            ver => 25,
+            sql => [
+                'ALTER TABLE objects ADD COLUMN oaliascache text',
+            ],
+        },
+        {
+            ver => '26',
+            sql => [ ],
+        },
+        {
+            ver => 27,
+            sql => [
+                q{
+                CREATE OR REPLACE VIEW nethost_attributes_netzone AS
+                SELECT netzone.name AS value, 'netZone' AS attr, nethost_attributes_ips.attr_key, nethost_attributes_ips.okey
+                FROM nethost_attributes_ips, netzone_attributes, netzone
+                WHERE netzone.ikey = netzone_attributes.okey AND netzone_attributes.attr = 'net'::text AND nethost_attributes_ips.value::inet <<= netzone_attributes.value::inet
+                EXCEPT
+                SELECT netzone.name AS value, 'netZone' AS attr, nethost_attributes_ips.attr_key, nethost_attributes_ips.okey
+                FROM nethost_attributes_ips, netzone_attributes, netzone
+                WHERE netzone.ikey = netzone_attributes.okey AND netzone_attributes.attr = 'netExclude'::text AND nethost_attributes_ips.value::inet <<= netzone_attributes.value::inet;
+                },
+                q{
+                CREATE OR REPLACE VIEW nethost_attributes_read AS
+                SELECT nethost_attributes.value, nethost_attributes.attr, nethost_attributes.attr_key, nethost_attributes.okey
+                FROM nethost_attributes
+                UNION
+                SELECT nethost_attributes_netzone.value, nethost_attributes_netzone.attr, nethost_attributes_netzone.attr_key, nethost_attributes_netzone.okey
+                FROM nethost_attributes_netzone;
+                }
+            ],
+        },
+        {
+            ver => 28,
+            sql => [
+                q{
+                CREATE TABLE attributes_values
+                (
+                -- HÃ©ritÃ©(e) from table revisions:  rev integer NOT NULL DEFAULT nextval('revisions_rev_seq'::regclass),
+                -- HÃ©ritÃ©(e) from table revisions:  date timestamp with time zone NOT NULL DEFAULT now(),
+                -- HÃ©ritÃ©(e) from table revisions:  "create" timestamp with time zone NOT NULL DEFAULT now(),
+                -- HÃ©ritÃ©(e) from table revisions:  ikey integer NOT NULL DEFAULT nextval('ikey_seq'::regclass),
+                otype text NOT NULL,
+                attributes text NOT NULL,
+                value text NOT NULL,
+                CONSTRAINT attributes_values_pkey PRIMARY KEY (ikey)
+                )
+                INHERITS (revisions);
+
+                CREATE UNIQUE INDEX attributes_values_idx
+                ON attributes_values
+                USING btree
+                (otype, attributes, value);
+                }
+            ]
+        },
+    );
+
+    my $dbi = $self->{_db};
+
+    foreach my $maj (@updates) {
+        if ($rev >= $maj->{ver}) {
+            next;
+        }
+        $self->log(LA_NOTICE, 'Switching to schema revision: ' . $maj->{ver} . "\n");
+        foreach my $sql (@{ $maj->{sql} }) {
+            {
+                my $sqlv = $sql;
+                $sqlv =~ s/^/  /mg;
+            }
+            my $err = $dbi->do($sql);
+            defined($err) or do {
+                $self->log(LA_ERR, "Erreur :\\" . $dbi->errstr . "\n");
+                return;
+            };
+        }
+
+        $self->log(LA_INFO, 'Updating schema_version to ' . $maj->{ver} . "\n");
+
+        $dbi->do(
+            'UPDATE settings SET val = ? where varname = ?',
+            undef,
+            $maj->{ver}, 'schema_version'
+        );
+    }
+
+    return 1;
+}
+
+sub _CreateInternalObjects {
+    my ($self) = @_;
+
+    my $dbi = $self->{_db};
+
+    my @objects = (
+        {
+            name => 'dpmt',
+            otype => 'sutype',
+            attrs => {
+                description => 'Department',
+            },
+        },
+        {
+            name => 'contrattype',
+            otype => 'sutype',
+            attrs => {
+                description => 'Contract',
+            },
+        },
+        {
+            name => '-useralias',
+            otype => 'group',
+            attrs => {
+                description => 'Internal group for user alias object',
+                gidnumber => -1,
+                unexported => 1,
+            },
+            intern => 1,
+        },
+    );
+
+    my $setnodel  = $dbi->prepare('UPDATE objects SET nodelete     = true where name = ?');
+    my $setintern = $dbi->prepare('UPDATE objects SET internobject = true where name = ?');
+
+    foreach (@objects) {
+        if (!$self->GetRawObject($_->{otype}, $_->{name})) {
+            printf("Creating object %s/%s\n", $_->{otype}, $_->{name});
+            $self->create_object($_->{otype}, $_->{name}, %{$_->{attrs} || {}})
+                or die sprintf("cannot create %s/%s\n", $_->{otype}, $_->{name});
+
+        }
+
+        $setnodel->execute($_->{name});
+        $setintern->execute($_->{name}) if ($_->{intern});
+    }
+
+    return 1;
+}
+
+=head2 SchemaUpgrade
+
+Upgrade the SQL schema
+
+=cut
+
+sub SchemaUpgrade {
+    my ($self) = @_;
+
+    return $self->temp_switch_unexported(
+        sub {
+            if($self->_SqlSchemaUpgrade) {
+                $self->log(LA_DEBUG, "upgrade::_SqlSchemaUpgrade done");
+            } else {
+                $self->{_db}->rollback;
+                return;
+            }
+
+            if ($self->_CreateInternalObjects) {
+                $self->log(LA_DEBUG, "upgrade::_CreateInternalObjects done");
+            } else {
+                $self->{_db}->rollback;
+                return;
+            }
+
+            return 1;
+        }, 1
+    );
+
+}
+
+1;
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Unix.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Unix.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Unix.pm	(revision 2119)
@@ -0,0 +1,462 @@
+package LATMOS::Accounts::Bases::Unix;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases);
+use LATMOS::Accounts::Log;
+use Fcntl qw(:flock);
+use Encode;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ad - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Accounts::Bases;
+  my $base = LATMOS::Accounts::Bases->new('unix');
+  ...
+
+=head1 DESCRIPTION
+
+Account base access over standard unix file format.
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 new(%config)
+
+Create a new LATMOS::Ad object for windows AD $domain.
+
+domain / server: either the Ad domain or directly the server
+
+ldap_args is an optionnal list of arguments to pass to L<Net::LDAP>.
+
+=cut
+
+sub new {
+    my ($class, %config) = @_;
+    
+    my $base = {
+        # are we using shadow, default to yes
+        use_shadow => (defined($config{use_shadow}) ? $config{use_shadow} : 1),
+        min_gid => $config{min_gid},
+        min_uid => $config{min_uid},
+        nis_overflow => ($config{nis_overflow} || ''),
+        users => {},
+        groups => {},
+    };
+
+    foreach (qw(passwd shadow group gshadow)) {
+        if ($config{$_}) {
+            $base->{$_} = $config{$_};
+        } elsif ($config{directory}) {
+            $base->{$_} = $config{directory} . '/' . $_;
+        } else {
+            $base->{$_} = "/etc/$_";
+        }
+    }
+
+
+    bless($base, $class);
+}
+
+my @password_fields = qw(account password uid gid gecos home shell);
+my @shadow_fields =   qw(account spassword last_changed before_ch after_ch exp_warn exp_disable disable res);
+my @group_fields =    qw(group_name passwd gid user_list);
+my @gshadow_fields =  qw(group_name spassword unknown suser_list);
+
+# All UNIX account file are colon separated field
+# This function factorize open/read/split fields
+
+sub _load_unix_file {
+    my ($self, $file, $callback) = @_;
+    my $enc = find_encoding($self->config('encoding') || 'iso-8859-14')
+        or do {
+        $self->log(LA_ERROR, "Cannot find encoding %s", $self->config('encoding') || 'iso-8859-15');
+        return;
+    };
+    open(my $handle, '<', $file) or do {
+        $self->log(LA_ERR, "Cannot open unix file `%s' for reading (%s)", $file, $!);
+        return;
+    };
+    $self->log(LA_DEBUG, "Reading file $file");
+    flock($handle, LOCK_EX);
+    while (my $line = <$handle>) {
+        chomp($line);
+        $line = encode('utf-8', $line);
+        my @ch = split(':', $line);
+        $callback->(@ch);
+    }
+    close($handle);
+    return 1;
+}
+
+=head2 load
+
+Read file and load data into memory
+
+=cut
+
+sub load {
+    my ($self) = @_;
+
+    # If already loaded, just say ok !
+    $self->{_loaded} and return 1;
+    
+    $self->_load_unix_file(
+        $self->{passwd},
+        sub {
+            my @ch = @_;
+            my $user = $ch[0] or return;
+            # TODO add check ?
+            foreach (@password_fields) {
+                $self->{users}{$user}{$_} = shift(@ch);
+            }
+            if ($self->{users}{$user}{password} =~ /^!!/) {
+                $self->{users}{$user}{password} =~ s/^!!//;
+                $self->{users}{$user}{locked} = 1;
+            }
+            $self->{users}{$user}{shell} ||= '';
+            $self->{users}{$user}{shell} =~ s/^-//;
+            $self->{users}{$user}{_id} = $user;
+        },
+    ) or do {
+        $self->log(LA_ERR, "Cannot parse %s", $self->{passwd});
+        return;
+    };
+    
+    $self->_load_unix_file(
+        $self->{group},
+        sub {
+            my @ch = @_;
+            my $group = $ch[0];
+
+            foreach (@group_fields) {
+                $self->{groups}{$group}{$_} = shift(@ch);
+            }
+            $self->{groups}{$group}{_id} = $group;
+
+            # split user in the group
+            foreach (split(',', ($self->{groups}{$group}{'user_list'} || ''))) {
+                $self->{groups}{$group}{'users'}{$_} = 1;
+            }
+        }
+    ) or do {
+        $self->log(LA_ERR, "Cannot parse %s", $self->{group});
+        return;
+    };
+
+    # Post processing group for split group because nis
+    foreach my $group (keys %{$self->{groups} || {}}) {
+        if (my ($realgroup) = $group =~ /(.*)_\d\d$/) {
+            if (exists($self->{groups}{$realgroup}) &&
+                $self->{groups}{$realgroup}{gid} == $self->{groups}{$group}{gid}) {
+               # for sure, it's the same
+                foreach (keys %{$self->{groups}{$group}{'users'} || {}}) {
+                    $self->{groups}{$realgroup}{'users'}{$_} = 1;
+                }
+                delete($self->{groups}{$group});
+            }
+        }
+    }
+
+    # using shadow ? then reading shadow file
+    if ($self->{use_shadow}) {
+
+    $self->_load_unix_file(
+        $self->{shadow},
+        sub {
+            my @ch = @_;
+            my $user = $ch[0];
+            foreach (@shadow_fields) {
+                $self->{users}{$user}{$_} = shift(@ch);
+            }
+            if ($self->{users}{$user}{spassword} =~ /^!!/) {
+                $self->{users}{$user}{spassword} =~ s/^!!//;
+                $self->{users}{$user}{locked} = 1;
+            }
+        }
+    ) or do {
+        $self->log(LA_ERR, "Cannot parse %s", $self->{shadow});
+        return;
+    };
+
+    $self->_load_unix_file(
+        $self->{gshadow},
+        sub {
+            my @ch = @_;
+            my $group = $ch[0];
+            # TODO add check ?
+            foreach (@gshadow_fields) {
+                $self->{groups}{$group}{$_} = shift(@ch);
+            }
+            # split user in the group
+            foreach (split(',', $self->{groups}{$group}{'suser_list'} || '')) {
+                $self->{groups}{$group}{'susers'}{$_} = 1;
+            }
+        }
+    ) or do {
+        $self->log(LA_ERR, "Cannot parse %s", $self->{gshadow});
+        return;
+    };
+
+    } # use shadow ?
+
+    $self->{_loaded} = 1;
+
+    1;
+}
+
+sub _save_unix_file {
+    my ($self, $file, @data) = @_;
+    my $enc = find_encoding($self->config('encoding') || 'iso-8859-14')
+        or do {
+        $self->log(LA_ERROR, "Cannot find encoding %s", $self->config('encoding') || 'iso-8859-15');
+        return;
+    };
+    open(my $handle, '>>', $file) or do {
+        la_log(LA_ERR, "Cannot open unix file `%s' for writing (%s)", $file, $!);
+        return;
+    };
+    flock($handle, LOCK_EX);
+    truncate($handle, 0);
+    foreach my $line (@data) {
+        my $string = join(':', map { my $f = $_; $f =~ s/:/;/g; $f } map { defined($_) ? $_ : '' } @$line) . "\n";
+        $string = decode('utf8', $string);
+        print $handle $string;
+    }
+    close($handle);
+    $self->log(LA_INFO, $file . " saved");
+    return 1;
+}
+
+sub _commit {
+    my ($self) = @_;
+
+    $self->_save_unix_file(
+        $self->{passwd},
+        map {[
+            $_,
+            ($self->{users}{$_}{locked}
+                ? '!!' . ($self->{users}{$_}{password} || '')
+                : ($self->{users}{$_}{password} || 'x')), # No empty pass !!
+            $self->{users}{$_}{uid},
+            $self->{users}{$_}{gid},
+            $self->{users}{$_}{gecos} || '',
+            $self->{users}{$_}{home} || '/dev/null',
+            ($self->{users}{$_}{locked} ? '-' : '') . ($self->{users}{$_}{shell}
+                || '/bin/false'),
+        ]} sort { $self->{users}{$a}{uid} <=> $self->{users}{$b}{uid} } keys %{$self->{users}}
+    ) or return;
+
+    my @grouplines = ();
+    foreach (
+        sort {  $self->{groups}{$a}{gid} <=> $self->{groups}{$b}{gid} }
+        keys %{$self->{groups}}) {
+        my @gline = ($_, $self->{groups}{$_}{password} || 'x',
+            $self->{groups}{$_}{gid},
+            join(',', sort keys %{$self->{groups}{$_}{users} || {}}),
+        );
+        if (length(join(':', @gline)) >= 1023) {
+            $self->{nis_overflow} ||= '';
+            if ($self->{nis_overflow} eq 'kill') {
+                $gline[3] = 'LINE_TOO_LONG';
+            } elsif ($self->{nis_overflow} eq 'truncate') {
+                my $len = length(join(':', @gline[0 .. 2])) + 1;
+                my @users = sort keys %{$self->{groups}{$_}{users} || {}};
+                while (@users && $len + length(join(',', @users)) >= 1023) {
+                    pop(@users);
+                }
+                $gline[3] = join(',', @users);
+            } elsif($self->{nis_overflow} eq 'split') {
+                my @users = sort keys %{$self->{groups}{$_}{users} || {}};
+                my $count = 0;
+                my @nextusers;
+                while (my $u = shift(@users)) {
+                    my $needflush = 0;
+                    if (length(join(':', @gline[0 .. 2])) + 1 +
+                        length(join(',', (@nextusers, $u))) >= 1023) {
+                        unshift(@users, $u);
+                        $needflush = 1;
+                    } else {
+                        push(@nextusers, $u);
+                    }
+                    if (!@users || $needflush) {
+                        push(@grouplines, [
+                            $gline[0] . ($count ? sprintf('_%02d', $count) :''),
+                            $gline[1],
+                            $gline[2],
+                            join(',', @nextusers) ]
+                        );
+                        @nextusers = (); $count++;
+                    }
+                }
+                next;
+            }
+        }
+        push(@grouplines, \@gline);
+    }
+    $self->_save_unix_file(
+        $self->{group},
+        @grouplines,
+    ) or return;
+
+    if ($self->{use_shadow}) {
+
+    $self->_save_unix_file(
+        $self->{shadow},
+        map {[
+            $_,
+            ($self->{users}{$_}{locked}
+                ? '!!' . ($self->{users}{$_}{spassword} || '')
+                : $self->{users}{$_}{spassword} || 'x'),
+            $self->{users}{$_}{last_changed},
+            $self->{users}{$_}{before_ch},
+            $self->{users}{$_}{after_ch},
+            $self->{users}{$_}{exp_warn},
+            $self->{users}{$_}{exp_disable},
+            $self->{users}{$_}{disable},
+            $self->{users}{$_}{res},
+        ]} sort { $self->{users}{$a}{uid} <=> $self->{users}{$b}{uid} } keys %{$self->{users}}
+    ) or return;
+
+
+    $self->_save_unix_file(
+        $self->{gshadow},
+        map {[
+            $_,
+            $self->{groups}{$_}{spassword} || 'x',
+            $self->{groups}{$_}{unknown},
+            join(',', keys %{$self->{groups}{$_}{susers} || {}}), 
+        ]} sort {  $self->{groups}{$a}{gid} <=> $self->{groups}{$b}{gid} } keys %{$self->{groups}}
+    ) or return;
+
+    } # If use shadow
+
+    1
+}
+
+sub list_objects {
+    my ($self, $otype) = @_;
+
+    # objects are store into sub ref:
+    my $internal_obj = {
+        user => 'users',
+        group => 'groups',
+    }->{$otype} or return;
+    my @obj = sort keys %{$self->{$internal_obj} || {} };
+    for ($otype) {
+        /^user$/ and
+            return grep { $self->{$internal_obj}{$_}{uid} >= ($self->{min_uid} || 0) } @obj;
+        /^group$/ and
+            return grep { $self->{$internal_obj}{$_}{gid} >= ($self->{min_gid} || 0) } @obj;
+    }
+}
+
+sub create_object {
+    my ($self, $otype, $id, %gdata) = @_;
+
+    my %data;
+
+    foreach my $attr (keys %gdata) {
+        if (my $oattr = $self->attribute($otype, $attr)) {
+            $data{ $oattr->iname } = $gdata{$attr};
+        } else {
+            $data{$attr} = $gdata{$attr};
+        }   
+    }
+
+    # objects are store into sub ref:
+    my $internal_obj = {
+        user => 'users',
+        group => 'groups',
+    }->{$otype};
+    for ($otype) {
+        if (/^user$/ && (!defined($data{uid}) || !defined($data{gid}))) {
+            $self->log(LA_ERR, "uid or gid missing to create $otype");
+            return;
+        }
+        if(/^group$/ && !defined($data{gid})) {
+            $self->log(LA_ERR, "gid missing to create $otype");
+            return;
+        }
+    }
+
+    if ($self->{$internal_obj}{$id}) {
+        $self->log(LA_ERR, "Object %s.%s already exists", $otype, $id);
+        return
+    }
+
+    $self->{$internal_obj}{$id} = {
+        account => $id,
+        uid => $data{uid},
+        gid => $data{gid},
+        _id => $id,
+    };
+
+    my $obj = $self->get_object($otype, $id) or do {
+        $self->log(LA_ERR, "Cannot find freshly create $otype $id");
+        return;
+    };
+
+    $obj->set_fields(%data) or return;
+    $obj
+}
+
+sub _delete_object {
+    my ($self, $otype, $id, %data) = @_;
+
+    # objects are store into sub ref:
+    my $internal_obj = {
+        user => 'users',
+        group => 'groups',
+    }->{$otype};
+    delete $self->{$internal_obj}{$id};
+    1
+}
+
+sub _rename_object {
+    my ($self, $otype, $id, $newid) = @_;
+    my $internal_obj = {
+        user => 'users',
+        group => 'groups',
+    }->{$otype};
+    
+    if (exists($self->{$internal_obj}{$newid})) {
+        $self->log(LA_ERR, 'cannot rename %s/%s, %s already exists',
+            $otype, $id, $newid);
+        return;
+   }
+
+   $self->{$internal_obj}{$newid} = $self->{$internal_obj}{$id};
+   delete($self->{$internal_obj}{$id});
+
+   1
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Unix/Group.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Unix/Group.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Unix/Group.pm	(revision 2119)
@@ -0,0 +1,143 @@
+package LATMOS::Accounts::Bases::Unix::Group;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Objects);
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev: 205 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Bases::Unix::Group - Group management in Unix Base
+
+=head1 SYNOPSIS
+
+  use LATMOS::Accounts::Bases;
+  my $base = LATMOS::Accounts::Bases->new('unix');
+  ...
+
+=head1 DESCRIPTION
+
+Access and modify group informations in Unix base.
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+    {
+        sAMAccountName => {
+            iname => 'group_name',
+            ro => 1,
+        },
+        gidNumber => {
+            iname => 'gid',
+        },
+        memberUID => {
+            iname => 'user_list',
+            multiple => 1,
+            delayed => 1,
+        },
+    };
+}
+
+=head2 new(%config)
+
+Create a new LATMOS::Ad object for windows AD $domain.
+
+domain / server: either the Ad domain or directly the server
+
+ldap_args is an optionnal list of arguments to pass to L<Net::LDAP>.
+
+=cut
+
+sub new {
+    my ($class, $base, $id, @args) = @_;
+
+    if (exists($base->{groups}{$id})) {
+        if ($base->{groups}{$id}{gid} < ($base->{min_gid} || 0)) {
+            $base->log(LA_DEBUG, "gid %d less than minimal %d", $base->{groups}{$id}{gid}, ($base->{min_gid} || 0));
+            return;
+        }
+        return bless($base->{groups}{$id}, $class);
+    } else {
+        $base->log(LA_ERR, "Cannot find group $id");
+        return
+    }
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+    if ($field eq 'user_list') {
+        return [ keys %{ $self->{'users'} || {} } ];
+    } elsif ($field eq 'group_name') {
+        return $self->id;
+    }
+
+    $self->{$field}
+}
+
+sub set_fields {
+    my ($self, %gdata) = @_;
+
+    my %data;
+
+    foreach my $attr (keys %gdata) {
+        if (my $oattr = $self->attribute($attr)) {
+            $data{ $oattr->iname } = $gdata{$attr};
+        } else {
+            $data{$attr} = $gdata{$attr};
+        }   
+    }
+
+    foreach my $field (keys %data) {
+        $field eq 'gid' and do {
+            if (!(defined($data{$field}) && $data{$field} =~ /^\d+$/)) {
+                $self->base->log(LA_ERR,
+                    "Invalid Group data for $field: " . (defined($data{$field}) ? $data{$field} : ''));
+                return;
+            }
+        };
+    }
+    foreach my $field (keys %data) {
+        # TODO check fields exists !
+        if ($field eq 'user_list') {
+            $self->{'users'} = {};
+            foreach (ref $data{$field} ? (@{ $data{$field} || [] }) : ($data{$field})) {
+                defined($_) or next;
+                $self->{'users'}{$_} = 1;
+            }
+        } else {
+            $self->{$field} = $data{$field};
+        }
+    }
+    1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Objects>
+L<LATMOS::Accounts::Bases::Unix>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Unix/User.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Unix/User.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Unix/User.pm	(revision 2119)
@@ -0,0 +1,193 @@
+package LATMOS::Accounts::Bases::Unix::User;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases::Objects);
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev: 205 $ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ad - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Accounts::Bases;
+  my $base = LATMOS::Accounts::Bases->new('unix');
+  ...
+
+=head1 DESCRIPTION
+
+Account base access over standard unix file format.
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+    {
+        uidNumber       => { iname => 'uid', uniq => 1, },
+        gidNumber       => { iname => 'gid',
+            can_values => sub {
+                map { $_->get_attributes('gidNumber') }
+                map { $base->get_object('group', $_) }
+                $base->list_objects('group')
+            },
+            reference => 'group',
+            mandatory => 1,
+        },
+        gecos           => { },
+        homeDirectory   => { iname => 'home' },
+        loginShell      => { iname => 'shell' },
+        userPassword    => {
+            iname => ($base->{use_shadow} ? 'spassword' : 'password'),
+        },
+        memberOf        => {
+            delayed => 1,
+            multiple => 1,
+        },
+        locked          => {},
+        givenName       => { ro => 1 },
+        sn              => { ro => 1 },
+        sAMAccountName  => { iname => 'login', ro => 1 },
+        $base->{use_shadow} ?
+        (
+            shadowLastChange => { iname => 'last_changed' },
+            shadowMin        => { iname => 'before_ch' },
+            shadowMax        => { iname => 'after_ch' },
+            shadowWarning    => { iname => 'exp_warn' },
+            shadowInactive   => { iname => 'exp_disable' },
+            shadowExpire     => { iname => 'disable' },
+            shadowFlag       => { iname => 'res' },
+        ) : (),
+    };
+}
+
+=head2 new(%config)
+
+=cut
+
+sub new {
+    my ($class, $base, $id, @args) = @_;
+    # we profit of ref, quite easy
+    if (exists($base->{users}{$id}) && $base->{users}{$id}) {
+        if ($base->{users}{$id}{uid} < ($base->{min_uid} || 0)) {
+            return;
+        }
+        return bless($base->{users}{$id}, $class);
+    } else { return }
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+    for ($field) {
+        /^login$/ and return $self->id;
+        /^(sn|givenName)$/ and do {
+            my $gecos = $self->{gecos} || '';
+            my ($given, $sn) = $gecos =~ /^([^, ]+) +([^, ]+)/;
+            return $field eq 'sn' ? $sn : $given;
+        };
+        /^memberOf$/ and do {
+            my @res;
+            foreach (sort keys %{ $self->base->{groups} || {}}) {
+                push(@res, $_)
+                    if(exists($self->base->{groups}{$_}{'users'}{$self->id}));
+            }
+            return \@res;
+        };
+    }
+    $self->{$field}
+}
+
+sub set_fields {
+    my ($self, %gdata) = @_;
+
+    my %data;
+
+    foreach my $attr (keys %gdata) {
+        if (my $oattr = $self->attribute($attr)) {
+            $data{ $oattr->iname } = $gdata{$attr};
+        } else {
+            $data{$attr} = $gdata{$attr};
+        }
+    }
+
+    foreach my $field (keys %data) {
+        $field =~ /^(uid|gid)$/ and do {
+            if(!(defined($data{$field}) && $data{$field} =~ /^\d+$/)) {
+                $self->base->log(LA_ERR,
+                    "Invalid data for $field: " . (defined($data{$field}) ? $data{$field} : '(none)'));
+                return;
+            }
+        }
+    }
+    foreach my $field (keys %data) {
+        # TODO check fields exists !
+        $field =~ /^(sn|givenName)$/ and next;
+        $field eq 'memberOf' and do {
+            my %set;
+            $data{$field} = [ $data{$field} ] unless(ref $data{$field});
+            foreach(grep { defined($_) }  @{ $data{$field} || []}) {
+                $set{$_} = 1;
+            }
+            foreach (sort keys %{ $self->base->{groups} || {}}) {
+                if ($set{$_}) {
+                    $self->base->{groups}{$_}{'users'}{$self->id} = 1;
+                } else {
+                    delete($self->base->{groups}{$_}{'users'}{$self->id});
+                }
+            }
+        };
+        $self->{$field} = $data{$field};
+    }
+    1;
+}
+
+=head2 _InjectCryptPasswd($cryptpasswd)
+
+Inject a password encrypted using standard UNIX method.
+
+=cut
+
+sub _InjectCryptPasswd {
+    my ($self, $cryptpasswd) = @_;
+
+    my $res = $self->set_c_fields(
+        userPassword => $cryptpasswd,
+    );
+
+    if ($res) {
+        $self->base->log(LA_NOTICE, 'Crypted password injected for %s', $self->id);
+        return 1;
+    } else {
+        $self->base->log(LA_ERR, 'Cannot inject crypted password for %s', $self->id);
+        return 0;
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases::Unix>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Zimbra.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Zimbra.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Zimbra.pm	(revision 2119)
@@ -0,0 +1,147 @@
+package LATMOS::Accounts::Bases::Zimbra;
+
+use 5.010000;
+use strict;
+use warnings;
+
+use base qw(LATMOS::Accounts::Bases);
+use LATMOS::Accounts::Log;
+use SOAP::Lite;
+use HTTP::Cookies;
+use XML::XPath;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Zimbra - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Accounts::Bases;
+  my $base = LATMOS::Accounts::Bases->new('unix');
+  ...
+
+=head1 DESCRIPTION
+
+Account base access over standard unix file format.
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 new(%config)
+
+Create a new LATMOS::Ad object for windows AD $domain.
+
+domain / server: either the Ad domain or directly the server
+
+ldap_args is an optionnal list of arguments to pass to L<Net::LDAP>.
+
+=cut
+
+sub new {
+    my ($class, %config) = @_;
+    
+    my $base = {
+        login => $config{login},
+        password => $config{password},
+        url => $config{url},
+        domain => $config{domain},
+    };
+
+    bless($base, $class);
+}
+
+sub DESTROY {
+    my ($self) = @_;
+    $self->{_db} && $self->{_db}->rollback;
+}
+
+=head2 load
+
+Read file and load data into memory
+
+=cut
+
+sub load {
+    my ($self) = @_;
+
+    $self->{soap} and return 1;
+
+    my $soap = SOAP::Lite->new();
+
+    $soap->proxy(
+        $self->{url},
+        ssl_opts => [
+            verify_hostname => 0,
+            SSL_verify_mode => 0x00,
+        ],
+        cookie_jar => HTTP::Cookies->new(ignore_discard => 1),
+    );
+
+    $soap->default_ns('urn:zimbraAdmin');
+    my $som = $soap->call('AuthRequest',
+        SOAP::Data->name('account')
+            ->value( $self->{login} )
+            ->attr({ by => 'name' }),
+        SOAP::Data->name('password')
+            ->value($self->{password}),
+        SOAP::Data->name('persistAuthTokenCookie')
+            ->value('1'),
+    );
+
+    if ($som->fault) {
+        la_log(LA_ERR, "Cannot connect to Zimbra using SOAP: %s", $som->faultstring);
+        return;
+    }
+
+    $soap->outputxml(1);
+    $self->{soap} = $soap;
+
+    1;
+}
+
+=head2 soapcall (@args)
+
+Perform a call to zimbra SOAP interface, return an C<XML::XPath> Object.
+
+Return nothing if an error is returned.
+
+=cut
+
+sub soapcall {
+    my ($self, @args) = @_;
+
+    my $xml = $self->{soap}->call(@args);
+
+    my $xpath = XML::XPath->new(xml => $xml);
+
+    if (my $node = $xpath->findnodes('//faultstring')) {
+        la_log(LA_ERR, "Saop error: %s", $node->string_value);
+        return;
+    }
+
+    return $xpath;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Zimbra/User.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Zimbra/User.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Zimbra/User.pm	(revision 2119)
@@ -0,0 +1,267 @@
+package LATMOS::Accounts::Bases::Zimbra::User;
+
+use 5.010000;
+use strict;
+use warnings;
+use overload '""' => 'stringify';
+
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+use base qw(LATMOS::Accounts::Bases::Objects);
+use SOAP::Lite;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Ad - Perl extension for blah blah blah
+
+=head1 DESCRIPTION
+
+Account base access over standard unix file format.
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _get_attr_schema {
+    my ($class, $base) = @_;
+
+    {
+        name => {
+            ro => 1,
+            get => sub {
+                my ($self) = @_;
+                return $self->object->{name}
+            },
+        },
+        zid => { ro => 1, }, # Zimbra internal id
+        uid => { ro => 1, },
+        cn => { },
+        sn => { },
+        givenName => { },
+        mail => { ro => 1, },
+        displayName => { },
+        objectClass => { ro => 1, multiple => 1, },
+        zimbraMailDeliveryAddress => { multiple => 1, ro => 1, },
+        zimbraMailAlias => { 
+            multiple => 1,
+            ro => 1,
+        },
+        aliases => {
+            multiple => 1,
+            get => sub {
+                my ($self) = @_;
+                return ref $self->object->{zinfo}{zimbraMailAlias}
+                    ? @{ $self->object->{zinfo}{zimbraMailAlias} }
+                    : $self->object->{zinfo}{zimbraMailAlias};
+            },
+            set => sub {
+                my ($self, $data) = @_;
+                my @zimbraMailAlias = ref $self->object->{zinfo}{zimbraMailAlias}
+                    ? @{ $self->object->{zinfo}{zimbraMailAlias} }
+                    : $self->object->{zinfo}{zimbraMailAlias};
+                my %currentalias = map { $_ => 1 } grep { $_ } @zimbraMailAlias;
+                my %newalias;
+                foreach (grep { $_ } (ref $data ? @{$data} : $data)) {
+                    # Adding domain to unqualified aliases
+                    $_ =~ /@/ or $_ .= '@' . $self->object->base->{domain};
+                    $newalias{lc($_)} = 1;
+                }
+                foreach (keys %currentalias) {
+                    $newalias{$_} and next;
+                    my $xpath = $self->object->base->soapcall(
+                        'RemoveAccountAliasRequest',
+                        SOAP::Data->name('id')->value($self->object->{zinfo}{zid}),
+                        SOAP::Data->name('alias')->value($_),
+                    ) or do {
+                        $self->object->base->la_log('Cannot remove alias $_');
+                        return;
+                    };
+                }
+                foreach (keys %newalias) {
+                    $currentalias{$_} and next;
+                    my $xpath = $self->object->base->soapcall(
+                        'AddAccountAliasRequest',
+                        SOAP::Data->name('id')->value($self->object->{zinfo}{zid}),
+                        SOAP::Data->name('alias')->value($_),
+                    ) or do {
+                        $self->object->base->la_log('Cannot add alias $_');
+                        return;
+                    };
+                }
+            }
+        },
+        zimbraCOSId => { },
+    }
+}
+
+sub new {
+    my ($class, $base, $id) = @_;
+
+    my %users = $class->_lists_account($base);
+
+    if (!$users{$id}) {
+        la_log(LA_DEBUG, "Cannot find user $id");
+        return;
+    } else {
+        my $user = bless {
+            _id => $id,
+            name => $users{$id},
+            _base => $base,
+        }, $class;
+        $user->_get_info();
+        return $user;
+    }
+}
+
+sub get_field {
+    my ($self, $field) = @_;
+
+    my $attribute = $self->attribute($field) or return;
+
+    return $self->{zinfo}{$field};
+}
+
+sub set_fields {
+    my ($self, %data) = @_;
+
+    my @values = ();
+
+    foreach my $key (keys %data) {
+        foreach my $val (ref $data{$key} ? @{ $data{$key} } : $data{$key}) {
+            push(@values, SOAP::Data->name('a')
+                ->attr({ 'n' => $key })
+                ->value($val)
+            );
+        }
+    }
+
+    my $xpath = $self->base->soapcall(
+        'ModifyAccountRequest',
+        SOAP::Data->name('id')->value($self->{zinfo}{zid}),
+        @values,
+    );
+
+}
+
+sub _create {
+    my ($class, $base, $id, %data) = @_;
+
+    my $pass = LATMOS::Accounts::Utils::genpassword();
+
+    my @values = ();
+
+    foreach my $key (keys %data) {
+        foreach my $val (ref $data{$key} ? @{ $data{$key} } : $data{$key}) {
+            push(@values, SOAP::Data->name('a')
+                ->attr({ 'n' => $key })
+                ->value($val)
+            );
+        }
+    }
+
+    my $xpath = $base->soapcall(
+        'CreateAccountRequest',
+        SOAP::Data->name('name')->value($id . '@' . $base->{domain}),
+        SOAP::Data->name('password')->value($pass),
+        @values,
+    );
+
+}
+
+sub _delete {
+    my ($class, $base, $id) = @_;
+
+    my $user = $base->get_object('user', $id) or do {
+        $base->log(LA_ERR, "Cannot get user $id");
+        return;
+    };
+
+    my $xpath = $base->soapcall(
+        'DeleteAccountRequest',
+        SOAP::Data->name('id')->value($user->{zinfo}{zid}),
+    );
+
+}
+
+sub _lists_account {
+    my ($class, $base) = @_;
+
+    my $xpath = $base->soapcall(
+        'GetAllAccountsRequest',
+        SOAP::Data->name('domain')->value($base->{domain})->attr({ by => 'name' }),
+    ) or do {
+        la_log(LA_ERR, "Error while trying to get users list");
+        return;
+    };
+
+    my %users;
+    foreach my $node ($xpath->findnodes('//account')) {
+        my $name = $node->getAttribute('name');
+        foreach ($node->getChildNodes) {
+            $_->getAttribute('n') eq 'uid' and do {
+                $users{$_->string_value} = $name;
+                last;
+            };
+        }
+    }
+
+    %users
+}
+
+sub _get_info {
+    my ($self) = @_;
+
+    my $xpath = $self->base->soapcall(
+        'GetAccountRequest',
+        SOAP::Data->name('account')->value($self->{name})->attr({ by => 'name' }),
+    ) or return;
+    require Data::Dumper;
+    #print Data::Dumper::Dumper($xpath);
+
+    my ($accountnode) = $xpath->findnodes('//account');
+
+    $self->{zinfo}{zid} = $accountnode->getAttribute('id');
+
+    foreach my $node ($xpath->findnodes('//a')) {
+        my $attr = $node->getAttribute('n');
+        if ($self->{zinfo}{$attr}) {
+            if (!ref $self->{zinfo}{$attr}) {
+                my $buf = $self->{zinfo}{$attr};
+                $self->{zinfo}{$attr} = [ $buf, $node->string_value ];
+            } else {
+                push(@{ $self->{zinfo}{$attr} }, $node->string_value);
+            }    
+        } else {
+            $self->{zinfo}{$attr} = $node->string_value;
+        }
+    }
+}
+
+sub list {
+    my ($class, $base) = @_;
+
+    my %users = $class->_lists_account($base);
+    return sort keys(%users);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Cli.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Cli.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Cli.pm	(revision 2119)
@@ -0,0 +1,1010 @@
+package LATMOS::Accounts::Cli;
+
+# $Id$
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+use Term::ReadLine;
+use Text::ParseWords;
+use Getopt::Long;
+
+=head1 NAME
+
+LATMOS::Accounts::Cli - Command line interface functions
+
+=head1 DESCRIPTION
+
+This module handle envirronment and functons for L<la-cli> tools.
+
+=cut
+
+{
+    open (my $fh, "/dev/tty" )
+        or eval 'sub Term::ReadLine::findConsole { ("&STDIN", "&STDERR") }';
+    die $@ if $@;
+    close ($fh);
+}
+
+my $term = Term::ReadLine->new('LA CLI');
+$term->MinLine(99999);
+my $OUT = $term->OUT || \*STDOUT;
+
+my $trans_mode = 0;
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 globalenv
+
+Return the main envirronement object
+
+=cut
+
+sub globalenv {
+    my ($labase) = @_;
+    my $env = LATMOS::Accounts::Cli->new({ prompt => sub { $_[0]->base->label . " cli > " }, },
+        $labase);
+    $env->add_func('unexported', {
+        help => 'unexported yes|no|show - switch or show base mode regarding' .
+            ' unexported objects',
+        completion => sub {
+            if (!$_[2]) {
+                return qw(yes no show);
+            }
+        },
+        code => sub {
+            my ($env, $arg) = @_;
+            if ($arg eq 'yes') {
+                $env->base->unexported(1);
+                print $OUT "Unexported are now show\n";
+            } elsif ($arg eq 'no') {
+                $env->base->unexported(0);
+                print $OUT "Unexported are no longer show\n";
+            } elsif ($arg eq 'show') {
+                print $OUT "Unexported objects " . ($env->base->unexported ?
+                "enable" : "disable") . "\n";
+            } else {
+                print $OUT "wrong argument\n";
+            }
+        },
+    });
+    $env->add_func('ls', {
+            help => 'ls object_type - list object of type object_type', 
+            completion => sub {
+                if(!$_[2]) {
+                    return $_[0]->base->list_supported_objects
+                } else { () }
+            },
+            code => sub {
+                if ($_[1]) {
+                    print $OUT map { "$_\n" } $_[0]->base->list_objects($_[1]);
+                } else {
+                    print $OUT "Object type missing\n";
+                }
+            },
+        });
+    $env->add_func('search', {
+            help => 'search objecttype filter1 [filter2...] - search object according filter',
+            completion => sub {
+                if(!$_[2]) {
+                    return $_[0]->base->list_supported_objects
+                } else { return() }
+            },
+            code => sub {
+                my ($env, @args) = @_;
+                if ($_[1]) {
+                    my @res = $env->base->search_objects(@args);
+                    print $OUT map { "$_\n" } @res;
+                    $env->{_lastsearch} = \@res;
+                    $env->{_lastsearchtype} = $args[0];
+                } else {
+                    print $OUT "Object type missing\n";
+                }
+            },
+        });
+    $env->add_func('expired', {
+        help => 'expired [delay] - list expired account more than delay (default is now)',
+        code => sub {
+            my ($env, $expire) = @_;
+            my @users = $env->base->find_expired_users($expire);
+            print $OUT map { "$_\n" } @users;
+            $env->{_lastsearchtype} = 'user';
+            $env->{_lastsearch} = \@users;
+        },
+    }) if ($env->base->can('find_expired_users'));
+    $env->add_func('expires', {
+        help => 'expires [delay] - list account expiring before delay (default is 1 month)',
+        code => sub {
+            my ($env, $expire) = @_;
+            my @users = $env->base->find_next_expire_users($expire);
+            print $OUT map { "$_\n" } @users;
+            $env->{_lastsearchtype} = 'user';
+            $env->{_lastsearch} = \@users;
+        },
+    }) if ($env->base->can('find_next_expire_users'));
+    $env->add_func('select', {
+            help => 'select object_type - select objects to perform action on it',
+            completion => sub {
+                if ($_[2]) {
+                    return $_[0]->base->list_objects($_[2]);
+                } else {
+                    return '@', $_[0]->base->list_supported_objects;
+                }
+            },
+            code => sub {
+                my ($env, $otype, @ids) = @_;
+                my @objs;
+                if ($otype eq '@') {
+                    if (@{$env->{_lastsearch}}) {
+                        $otype = $env->{_lastsearchtype};
+                        @ids = @{$env->{_lastsearch}};
+                    } else {
+                        print $OUT "No results store from previous search\n";
+                        return;
+                    }
+                }
+                if (!@ids) {
+                    print $OUT 'not enough arguments' . "\n";
+                    return;
+                }
+                foreach (@ids) {
+                    my $obj = $env->base->get_object($otype, $_) or do {
+                        print $OUT "Cannot get $otype $_\n";
+                        return;
+                    };
+                    push(@objs, $obj);
+                }
+                print $OUT "Selecting $otype " . join(', ', @ids) . "\n";
+                objenv($_[0]->base, $otype, @objs)->cli();
+            },
+        });
+    $env->add_func('create', {
+            code => sub {
+                my ($env, $otype) = @_;
+                my $helper = $env->base->ochelper($otype);
+                my $info = undef;
+                while (1) {
+                    my $status;
+                    ($status, $info) = $helper->step($info);
+
+                    if ($status ne 'NEEDINFO') {
+                        if ($status eq 'CREATED') {
+                            print $OUT "Object created\n";
+                            $env->commit;
+                        } else {
+                            print $OUT "Nothing done\n";
+                            $env->rollback;
+                        }
+                        return;
+                    }
+
+                    if ($info->{name}{ask}) {
+                        my $line = $term->readline("Name of the object ?");
+                        $info->{name}{content} = $line;
+                    }
+                    foreach my $attr (@{$info->{ask} || []}) {
+                        $term->Attribs->{completion_function} = sub {
+                            $info->{contents}{$attr}
+                        };
+                        my $line = $term->readline(sprintf('  %s %s? ',
+                                $attr,
+                                $info->{contents}{$attr}
+                                ? '(' . $info->{contents}{$attr} . ') '
+                                : ''
+                            ));
+                        $info->{contents}{$attr} = $line if($line);
+                    }
+                }
+            },
+        }
+    );
+    $env->add_func('exchangeip', 
+        {
+            help => 'Exchange two IP on host',
+            code => sub {
+                my ($env, @args) = @_;
+                my ($ip1, $ip2) =
+                    grep { $_ && $_ =~ /\d+\.\d+\.\d+\.\d+/ } @args;
+                if (!$ip2) {
+                    print $OUT "Need two ip to exchange\n";
+                    return;
+                }
+                if ($env->base->nethost_exchange_ip($ip1, $ip2)) {
+                    print $OUT "$ip1 and $ip2 get exchange\n";
+                    $env->commit;
+                } else {
+                    $env->rollback;
+                }   
+            },
+            completion => sub {
+                my ($env, $carg, @args) = @_;
+                if ($args[-1] && $args[-1] !~ m/\d+\.\d+\.\d+\.\d+/) {
+                    if (my $obj = $env->base->get_object('nethost', $args[-1])) {
+                        return $obj->get_attributes('ip');
+                    }
+                } else {
+                    my @list = 
+                    ($env->base->attributes_summary('nethost', 'ip'),
+                        $env->base->list_objects('nethost'));
+                    return @list;
+                }
+            },
+        }
+    );
+    $env->add_func('user',  { alias => [qw'select user' ] });
+    $env->add_func('group', { alias => [qw'select group'] });
+    return $env
+}
+
+=head2 objenv ($labase, $otype, @objs)
+
+Return a C<cli> envirronment over object.
+
+=cut
+
+sub objenv {
+    my ($labase, $otype, @objs) = @_;
+    my $objenv = LATMOS::Accounts::Cli->new(
+        {
+            prompt => sub {
+                sprintf("%s %s/%s > ",
+                    $_[0]->base->label,
+                    $_[0]->{_otype},
+                    @{$_[0]->{_objects}} > 1 ? '(' .
+                    scalar(@{$_[0]->{_objects}}) . ' obj.)' : $_[0]->{_objects}[0]->id,
+                );
+            },
+        },
+        $labase
+    );
+    $objenv->{_otype} = $otype;
+    $objenv->{_objects} = [ @objs ];
+    $objenv->add_func('+', {
+        help => 'add item to selection',
+        code => sub {
+            my ($env, @ids) = @_;
+            my %ids = map { $_->id => 1 } @{$env->{_objects}};
+            foreach (@ids) {
+                $ids{$_} and next;
+                my $o = $env->base->get_object($env->{_otype}, $_) or next;
+                push(@{$env->{_objects}}, $o);
+            }
+            printf $OUT "select is now %s: %s\n", $env->{_otype}, join(', ', map {
+                $_->id } @{$env->{_objects}});
+        },
+        completion => sub {
+            my ($env, undef, @ids) = @_;
+            my %ids = map { $_->id => 1 } @{$env->{_objects}};
+            return ( grep { ! $ids{$_} } $env->base->list_objects($env->{_otype}));
+        },
+        }
+    );
+    $objenv->add_func('-', {
+        help => 'add item to selection',
+        code => sub {
+            my ($env, @ids) = @_;
+            my %ids = map { $_ => 1 } @ids;
+            my @newobjs = grep { !$ids{$_->id} } @{$env->{_objects}};
+
+            if (!@newobjs) {
+                print $OUT "This would remove all objects from the list...\n";
+                return;
+            } else {
+                @{$env->{_objects}} = @newobjs;
+            }
+            printf $OUT "select is now %s: %s\n", $env->{_otype}, join(', ', map {
+                $_->id } @{$env->{_objects}});
+        },
+        completion => sub {
+            my ($env, undef, @ids) = @_;
+            my %ids = map { $_ => 1 } @ids;
+            grep { !$ids{$_} } map { $_->id } @{$env->{_objects}};
+        },
+        }
+    );
+    $objenv->add_func('show', {
+        help => 'show attributes - show an attributes of object',
+        code => sub {
+            my ($env, $attr) = @_;
+            if (!$attr) {
+                foreach (@{$env->{_objects}}) {
+                    print $OUT $_->dump;
+                }
+            } else {
+                foreach my $u (@{$env->{_objects}}) {
+                    print $OUT sort map { $u->id . ': ' .($_ || '') . "\n" } $u->get_attributes($attr);
+                }
+            }
+        },
+        completion => sub {
+            if (!$_[2]) {
+                return $_[0]->base->list_canonical_fields($_[0]->{_otype}, 'r')
+            }
+        },
+    });
+    $objenv->add_func('print', {
+        help => 'print fmt - show attributes using template',
+        code => sub {
+            my ($env, $fmt) = @_;
+            if (!defined($fmt)) {
+                print $OUT "no format given";
+                return;
+            }
+            foreach (@{$env->{_objects}}) {
+                print $OUT $_->queryformat($fmt) . "\n";
+            }
+        },
+    });
+    $objenv->add_func('unset', {
+        help => 'unset attribute - unset specified attribute',
+        code => sub {
+            my ($env, $attr) = @_;
+            $attr or do {
+                print $OUT "Attributes must be specified";
+                return;
+            };
+            foreach (@{$env->{_objects}}) {
+                defined $_->set_c_fields($attr => undef) or do {
+                    print $OUT "cannot unset attributes $attr for " . $_->id .
+                    "\n";
+                    return;
+                };
+            }
+            $env->commit;
+            print $OUT "Changes applied\n";
+        },
+        completion => sub {
+            my ($env, $lastw, @args) = @_;
+            if (!$args[0]) {
+                return $env->base->list_canonical_fields($env->{_otype}, 'w')
+            }
+        },
+    });
+    $objenv->add_func('set', {
+        help => 'set attribute value - set an attributes to single value "value"',
+        code => sub {
+            my ($env, $attr, @value) = @_;
+            @value or do {
+                print $OUT "attribute and value must be specified\n";
+                return;
+            };
+            foreach (@{$env->{_objects}}) {
+                defined $_->set_c_fields($attr => @value <= 1 ? $value[0] :
+                    \@value) or do {
+                    $_->base->rollback;
+                    printf $OUT "Cannot set $attr to %s for %s\n", join(', ',
+                        @value), $_->id;
+                    return;
+                };
+            }
+            $env->commit;
+            print $OUT "Done.\n";
+        },
+        completion => sub {
+            my ($env, $lastw, @args) = @_;
+            if (!$args[0]) {
+                return $env->base->list_canonical_fields($env->{_otype}, 'w')
+            } else {
+                my $attr = $env->base->attribute($env->{_otype}, $args[0]);
+                if ($attr->has_values_list) {
+                    $attr->can_values;
+                } elsif (@{$env->{_objects}} == 1) {
+                    return
+                    $env->{_objects}[0]->get_attributes($args[0]);
+                }
+            }
+        },
+    });
+    $objenv->add_func('add', {
+        help => 'add a value to an attribute',
+        code => sub {
+            my ($env, $attr, @value) = @_;
+            @value or do {
+                print $OUT "attribute and value must be specified\n";
+                return;
+            };
+            foreach (@{$env->{_objects}}) {
+                my @attrv = grep { $_ } $_->get_attributes($attr);
+                defined $_->set_c_fields($attr => [ @attrv, @value ]) or do {
+                    $_->base->rollback;
+                    printf $OUT "Cannot set $attr to %s for %s\n", join(', ',
+                        @value), $_->id;
+                    return;
+                };
+            }
+            $env->commit;
+            print $OUT "done\n";
+        },
+        completion => sub {
+            my ($env, $lastw, @args) = @_;
+            if (!$args[0]) {
+                return grep {
+                    $env->base->attribute($env->{_otype}, $_)->{multiple}
+                } $env->base->list_canonical_fields($env->{_otype}, 'w')
+            } else {
+                my $attr = $env->base->attribute($env->{_otype}, $args[0]);
+                if ($attr->has_values_list) {
+                    $attr->can_values;
+                } elsif (@{$env->{_objects}} == 1) {
+                    return
+                    $env->{_objects}[0]->get_attributes($args[0]);
+                }
+            }
+        },
+    });
+    $objenv->add_func('remove', {
+        help => 'remove a value from an attribute',
+        code => sub {
+            my ($env, $attr, @value) = @_;
+            @value or do {
+                print $OUT "attribute and value must be specified\n";
+                return;
+            };
+            foreach (@{$env->{_objects}}) {
+                my @attrv = grep { $_ } $_->get_attributes($attr);
+                foreach my $r (@value) {
+                    @attrv = grep { $_ ne $r } @attrv;
+                }
+                defined $_->set_c_fields($attr => @attrv ? [ @attrv ] : undef) or do {
+                    $_->rollback;
+                    printf $OUT "Cannot set $attr to %s for %s\n", join(', ',
+                        @value), $_->id;
+                    return;
+                };
+            }
+            $env->commit;
+            print $OUT "done\n";
+        },
+        completion => sub {
+            my ($env, $lastw, @args) = @_;
+            if (!$args[0]) {
+                return grep {
+                    $env->base->attribute($env->{_otype}, $_)->{multiple}
+                } $env->base->list_canonical_fields($env->{_otype}, 'w')
+            } else {
+                my $attr = $env->base->attribute($env->{_otype}, $args[0]);
+                if (@{$env->{_objects}} == 1) {
+                    return
+                    $env->{_objects}[0]->get_attributes($args[0]);
+                }
+            }
+        },
+    });
+    $objenv->add_func('list', {
+        help => 'list current selected objects',
+        code => sub {
+            printf $OUT "%s: %s\n", $_[0]->{_otype}, join(', ', map { $_->id }
+            @{$_[0]->{_objects}});
+        }
+    });
+    $objenv->add_func('edit', {
+            help => 'edit [object] - edit selected object using vi',
+            completion => sub {
+                return map { $_->id } @{$_[0]->{_objects}}
+            },
+            code => sub {
+                my ($env, $id) = @_;
+                my $obj;
+                if ($id) {
+                    $obj = grep { $_->id = $id } @{$env->{_objects}} or do {
+                        print $OUT "$id is not part of selected objects\n";
+                        return;
+                    };
+                } elsif (@{$env->{_objects}} == 1) {
+                    $obj = $env->{_objects}[0]
+                } else {
+                    print $OUT "multiple objects selected but can edit only one,"
+                    . "please specify which one\n";
+                    return;
+                }
+                my $res = LATMOS::Accounts::Utils::dump_read_temp_file(
+                    sub {
+                        my ($fh) = @_;
+                        $obj->text_dump($fh,
+                            {
+                                empty_attr => 1,
+                                only_rw => 1,
+                            }
+                        );
+                    },
+                    sub {
+                        my ($fh) = @_;
+                        my %attr = LATMOS::Accounts::Utils::parse_obj_file($fh);
+                        my $res = $obj->set_c_fields(%attr);
+                        if ($res) {
+                            print $OUT "Changes applied\n";
+                            $env->commit;
+                        }
+                        else { print $OUT "Error applying changes\n" }
+                        return $res ? 1 : 0;
+                    }
+                );
+            },
+        });
+    $objenv->add_func('delete', {
+        help => 'delete - delete selected object',
+        code => sub {
+            my ($env) = @_;
+            printf $OUT "%s: %s\ndelete selected objects ? (yes/NO)\n",
+            $env->{_otype}, join(', ', map { $_->id } @{$env->{_objects}});
+            my $reply = <STDIN> || ''; chomp($reply);
+            if ($reply eq 'yes') {
+                foreach (@{$env->{_objects}}) {
+                    $env->base->delete_object($env->{_otype}, $_->id) or do {
+                        print $OUT "Cannot delete " . $_->id . "\n";
+                        return;
+                    };
+                }
+                $env->commit;
+                return "EXIT";
+            } else {
+                print $OUT "cancel !\n"
+            }
+        },
+    });
+    if (grep { $objenv->base->attribute($otype, $_)->reference }
+        $objenv->base->list_canonical_fields($otype, 'r')) {
+        $objenv->add_func('select', {
+            help => 'select attribute [object]',
+            code => sub {
+                my ($env, $attrname, @objects) = @_;
+                my $totype = $env->base->attribute($env->{_otype},
+                    $attrname)->reference or return;
+
+                if (! @objects) {
+                    @objects = grep { $_ } 
+                      map { $_->get_attributes($attrname) } @{$env->{_objects}};
+                }
+                {
+                    my %uniq = map { $_ => 1 } @objects;
+                    @objects = keys %uniq;
+                }
+                my @objs = (grep { $_ } map { $env->base->get_object($totype, $_) }
+                        @objects);
+                return if (!@objs);
+                print $OUT "Selecting $otype " . join(', ', map { $_->id } @objs) . "\n";
+                objenv($_[0]->base, $totype, @objs)->cli();
+            },
+            completion => sub {
+                if ($_[2]) {
+                    my $totype = $_[0]->base->attribute($_[0]->{_otype},
+                        $_[2])->reference or return;
+                    return grep { $_ }
+                           map { $_->get_attributes($_[2]) }
+                           @{$_[0]->{_objects}};
+                } else {
+                    return grep { $_[0]->base->attribute($otype, $_)->reference }
+                    $_[0]->base->list_canonical_fields($otype, 'r');
+                }
+            },
+            }
+        );
+    }
+
+    if (lc($otype) eq 'user') {
+        $objenv->add_func('group', {
+            help => 'group add|remove|primary goupname',
+            code => sub {
+                my ($env, $action, @groups) = @_;
+                foreach my $obj (@{$env->{_objects}}) {
+                    if ($action eq 'primary') {
+                        my $gid = $groups[0];
+                        if ($gid !~ /^\d/) {
+                            my $gobj = $env->base->get_object('group', $gid) or
+                            do {
+                                print $OUT "Cannot find group $gid\n";
+                                return;
+                            };
+                            $gid = $gobj->get_attributes('gidNumber');
+                        }
+                        $obj->set_c_fields('gidNumber', $gid);
+                    } else {
+                        my %gr;
+                        foreach ($obj->get_attributes('memberOf')) {
+                            $gr{$_} = 1;
+                        }
+                        if ($action eq 'add') {
+                            $gr{$_} = 1 foreach(@groups);
+                        } elsif ($action eq 'remove') {
+                            delete($gr{$_}) foreach(@groups);
+                        } else {
+                            print $OUT 'invalid action' . "\n";
+                            return;
+                        }
+                        defined $obj->set_c_fields('memberOf' => [ keys %gr ]) or do {
+                            print $OUT "cannot set memberOf attributes for " .
+                            $obj->id . "\n";
+                            return;
+                        };
+                    }
+                }
+                $env->commit;
+            },
+            completion => sub {
+                if (!$_[2]) {
+                    return (qw(add remove primary));
+                } else {
+                    if ($_[2] eq 'remove') {
+                        my %uniq = map { $_ => 1 }
+                            grep { $_ }
+                            map { $_->get_attributes('memberOf') }
+                            @{$_[0]->{_objects}};
+                        return sort keys %uniq;
+                    } else {
+                        return $_[0]->base->search_objects('group');
+                    }
+                }
+            },
+        });
+    } elsif ($otype eq 'group') {
+        $objenv->add_func('member', {
+            help => 'member add|remove user',
+            code => sub {
+                my ($env, $action, @groups) = @_;
+                foreach my $obj (@{$env->{_objects}}) {
+                    my %gr;
+                    foreach ($obj->get_attributes('memberUID')) {
+                        $gr{$_} = 1;
+                    }
+                    if ($action eq 'add') {
+                        $gr{$_} = 1 foreach(@groups);
+                    } elsif ($action eq 'remove') {
+                        delete($gr{$_}) foreach(@groups);
+                    } else {
+                        print $OUT 'invalid action' . "\n";
+                        return;
+                    }
+                    defined $obj->set_c_fields('memberUID' => [ keys %gr ]) or do {
+                        print $OUT "cannot set memberUID attributes for " .
+                        $obj->id . "\n";
+                        return;
+                    };
+                }
+                $env->commit;
+            },
+            completion => sub {
+                if (!$_[2]) {
+                    return (qw(add remove));
+                } else {
+                    if ($_[2] eq 'remove') {
+                        my %uniq = map { $_ => 1 }
+                            grep { $_ }
+                            map { $_->get_attributes('member') }
+                            @{$_[0]->{_objects}};
+                        return sort keys %uniq;
+                    } else {
+                        return $_[0]->base->search_objects('user');
+                    }
+                }
+            },
+        });
+    }
+
+    return $objenv;
+}
+
+=head1 OBJECT FUNCTIONS
+
+=head2 new ($env, $labase)
+
+Create an envirronment object.
+
+C<$env> is functions descriptions.
+
+=cut
+
+sub new {
+    my ($class, $env, $labase) = @_;
+    bless($env, $class);
+    $env->{_labase} = $labase;
+
+    if ($labase->is_transactionnal) {
+        $env->add_func(
+            'transaction', {
+                help => 'change transaction mode',
+                code => sub {
+                    $trans_mode = $_[1] eq 'on' ? 1 : 0;
+                },
+                completion => sub {
+                    $trans_mode == 0 ? 'on' : 'off';
+                },
+            }
+        );
+        $env->add_func(
+            'commit', {
+                help => 'commit pending change',
+                code => sub {
+                    $_[0]->_commit;
+                },
+            }
+        );
+        $env->add_func(
+            'rollback', {
+                help => 'commit pending change',
+                code => sub {
+                    $_[0]->_rollback;
+                },
+            }
+        );
+    }
+    if ($labase->can('CreateAlias')) {
+        $env->add_func(
+            'newalias', {
+                help => 'Create an alias object',
+                code => sub {
+                    my ($self, $otype, $name, $for) = @_;
+                    if ($self->base->CreateAlias($otype, $name, $for)) {
+                        print $OUT "Alias $otype/$name Created\n";
+                        $self->commit;
+                    }
+                },
+                completion => sub {
+                    if ($_[3]) {
+                        return $_[0]->base->list_objects($_[2]);
+                    } elsif (!$_[2]) {
+                        return $_[0]->base->list_supported_objects;
+                    } else {
+                        return;
+                    }
+                }
+            },
+        );
+        $env->add_func(
+            'rmalias', {
+                help => 'Remove an alias object',
+                code => sub {
+                    my ($self, $otype, $name) = @_;
+                    if ($self->base->RemoveAlias($otype, $name)) {
+                        print $OUT "Alias $otype/$name Removed\n";
+                        $self->commit;
+                    }
+                },
+                completion => sub {
+                    if (!$_[2]) {
+                        return $_[0]->base->list_supported_objects;
+                    } else {
+                        return $_[0]->base->search_objects($_[2], 'oalias=*');
+                    }
+                }
+            },
+        );
+        $env->add_func(
+            'updalias', {
+                help => 'Update an alias object',
+                code => sub {
+                    my ($self, $otype, $name, $for) = @_;
+                    my $obj = $self->base->GetAlias($otype, $name) or do {
+                        print $OUT "No alias $otype/$name found";
+                        return;
+                    };
+                    if ($obj->set_c_fields(oalias => $for)) {
+                        print $OUT "Alias $otype/$name Updated\n";
+                        $self->commit;
+                    }
+                },
+                completion => sub {
+                    if ($_[3]) {
+                        return $_[0]->base->list_objects($_[2]);
+                    } elsif($_[2]) {
+                        return $_[0]->base->search_objects($_[2], 'oalias=*');
+                    } else {
+                        return $_[0]->base->list_supported_objects;
+                    }
+                }
+            },
+        );
+    }
+    $env->add_func('quit', { help => 'quit - exit the tool',
+            code => sub { print "\n"; exit(0) }, });
+    $env->add_func('exit', { help => "exit current mode",
+            code => sub { return "EXIT" }, });
+    $env->add_func('help', {
+        help => 'help [command] - print help about command',
+        completion => sub {
+            if (!$_[2]) { return sort keys %{ $_[0]->{funcs} || {}} }
+        },
+        code => sub {
+            my ($self, $name) = @_;
+            if (!$name) {
+                print $OUT join(', ', sort keys %{ $self->{funcs} || {}}) . "\n";
+            } elsif ($self->{funcs}{$name}{alias}) {
+                print $OUT "$name is an alias for " . join(' ',
+                    @{$self->{funcs}{$name}{alias}}) . "\n";
+            } elsif ($self->{funcs}{$name}{help}) {
+                print $OUT $self->{funcs}{$name}{help} . "\n";
+            } else {
+                print $OUT "No help availlable\n";
+            }
+        },
+    });
+
+    $env;
+}
+
+=head2 base
+
+Return the attached base object.
+
+=cut
+
+sub base { $_[0]->{_labase} }
+
+=head2 cli
+
+Start the main loop
+
+=cut
+
+sub cli {
+    my ($self) = @_;
+    while (1) {
+        $term->Attribs->{completion_function} = sub {
+            $self->complete($_[0], shellwords(substr($_[1], 0, $_[2])));
+        };
+        defined (my $line = $term->readline($self->prompt)) or do {
+            print $OUT "\n";
+            return;
+        };
+        $term->addhistory($line);
+        my $res = $self->run(shellwords($line));
+        $self->rollback if (!$trans_mode);
+        if ($res && $res eq 'EXIT') { print $OUT "\n"; return }
+    }
+}
+
+=head2 prompt
+
+Wait user to input command
+
+=cut
+
+sub prompt {
+    my ($self) = @_;
+    if (!$self->{prompt}) {
+        return "LA cli > ";
+    } else {
+        $self->{prompt}->($self);
+    }
+}
+
+=head2 add_func ($name, $param)
+
+Add new function in the envirronment
+
+=cut
+
+# TODO: hide this
+
+sub add_func {
+    my ($self, $name, $param) = @_;
+    $self->{funcs}{$name} = $param;
+}
+
+=head2 getoption ($opt, @args)
+
+Parse commmand line
+
+=cut
+
+# TODO: hide this
+
+sub getoption {
+    my ($self, $opt, @args) = @_;
+    local @ARGV = @args;
+    Getopt::Long::Configure("pass_through");
+    GetOptions(%{ $opt });
+
+    return @ARGV;
+}
+
+# TODO: useless ?
+
+sub _parse_arg {
+    my ($self, $name, @args) = @_;
+    return @args;
+}
+
+=head2 complete
+
+Return possible words according current entered words
+
+=cut
+
+sub complete {
+    my ($self, $lastw, $name, @args) = @_;
+    if (!$name) {
+        return grep { /^\Q$lastw\E/ } sort
+            (keys %{ $self->{funcs} || {}});
+    } elsif ($self->{funcs}{$name}{alias}) {
+        $self->complete($lastw, @{$self->{funcs}{$name}{alias}}, @args);
+    } elsif ($self->{funcs}{$name}{completion}) {
+        my @pargs = $self->_parse_arg($name, @args);
+        return map { my $t = $_; $t =~ s/\s/\\ /g; $t } grep { $_ && /^\Q$lastw\E/ } $self->{funcs}{$name}{completion}->($self, $lastw, @pargs);
+    } else {
+        return ();
+    }
+}
+
+=head2 run ($name, @args)
+
+Run functions
+
+=cut
+
+sub run {
+    my ($self, $name, @args) = @_;
+    return if (!$name);
+    if (!exists($self->{funcs}{$name})) {
+        print $OUT "No command $name found\n";
+    } elsif ($self->{funcs}{$name}{alias}) {
+        $self->run(@{$self->{funcs}{$name}{alias}}, @args);
+    } elsif ($self->{funcs}{$name}{code}) {
+        my @pargs = $self->_parse_arg($name, @args);
+        $self->{funcs}{$name}{code}->($self, @args);
+    } else {
+        print $OUT "No command $name found\n";
+    }
+}
+
+=head2 commit
+
+Call commit to base unelss in transaction mode
+
+=cut
+
+sub commit {
+    my ($self) = @_;
+    if ($trans_mode) {
+    } else {
+        $self->_commit;
+    }
+}
+
+sub _commit {
+    my ($self) = @_;
+    $self->base->commit;
+}
+
+=head2 rollback
+
+Perform rollback unless in transaction mode
+
+=cut
+
+sub rollback {
+    my ($self) = @_;
+    if ($trans_mode) {
+        print $OUT "All pending changes get rollback\n";
+    }
+    $self->_rollback;
+}
+
+sub _rollback {
+    my ($self) = @_;
+    $self->base->rollback;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/I18N.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/I18N.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/I18N.pm	(revision 2119)
@@ -0,0 +1,69 @@
+package LATMOS::Accounts::I18N;
+use 5.010000;
+use strict;
+use warnings;
+use Exporter ();
+use vars qw(@ISA @EXPORT_OK @EXPORT);
+use utf8;
+use LATMOS::Accounts::Log;
+use Locale::gettext;
+use POSIX ();
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Utils - Utils functions
+
+=cut
+
+@ISA = qw(Exporter);
+@EXPORT = 'l';
+@EXPORT_OK = 'l';
+
+my $init;
+
+if (!$init) {
+    if (-d 'po') {
+        Locale::gettext::bindtextdomain('linkaccounts', 'po')
+    }
+    Locale::gettext::bind_textdomain_codeset('linkaccounts', 'UTF-8');
+    $init = 1;
+}
+
+=head1 FUNCTIONS
+
+=head2 l($text, @args)
+
+Return the translate messsage, using printf.
+
+=cut
+
+sub l { 
+    my ($text, @args) = @_;
+    my $trans = Locale::gettext::dgettext('linkaccounts', $text || '');
+
+    return sprintf($trans || $text, @args);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<sudo>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Log.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Log.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Log.pm	(revision 2119)
@@ -0,0 +1,257 @@
+package LATMOS::Accounts::Log;
+
+use strict;
+use warnings;
+use Sys::Syslog qw(:standard :macros);
+use Exporter ();
+use Mail::Sendmail;
+
+=head1 NAME
+
+LATMOS::Accounts::Log - Log dispatcher for LATMOS::Accounts
+
+=head1 SYNOPSYS
+
+    use LATMOS::Accounts::Log
+    la_log(LA_ERR, "An error has occur");
+
+=head1 DESCRIPTION
+
+This module provide facilities to log to both console, syslog or using callback.
+
+When the environement variable C<LA_DEBUG> is set, all message are print to
+C<stderr>.
+
+=cut
+
+our @loglevels = qw(
+    LA_EMERG
+    LA_ALERT
+    LA_CRIT
+    LA_ERR
+    LA_ERROR
+    LA_WARN
+    LA_WARNING
+    LA_NOTICE
+    LA_INFO
+    LA_DEBUG
+);
+
+use vars qw(@EXPORT_OK @EXPORT %EXPORT_TAGS @ISA);
+@ISA = qw(Exporter);
+@EXPORT = (qw(
+    la_set_log la_log
+    ), @loglevels);
+
+@EXPORT_OK = @EXPORT;
+%EXPORT_TAGS = (LOGLEVELS => [ @loglevels ]);
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+my %lastmessages = ();
+
+=head1 LOG LEVEL
+
+=over 4
+
+=item LA_EMERG
+
+=cut
+
+sub LA_EMERG { LOG_EMERG } # system is unusable
+
+=item LA_ALERT
+
+=cut
+
+sub LA_ALERT { LOG_ALERT } # action must be taken immediately
+
+=item LA_CRIT
+
+=cut
+
+sub LA_CRIT { LOG_CRIT } # critical conditions
+
+=item LA_ERR, LA_ERROR
+
+=cut
+
+sub LA_ERR   { LOG_ERR } # error conditions
+sub LA_ERROR { LA_ERR } # Alias for ERR
+
+=item LA_WARN, LA_WARNING
+
+=cut
+
+sub LA_WARNING { LOG_WARNING } # warning conditions
+sub LA_WARN    { LA_WARNING } # warning conditions
+
+=item LA_NOTICE
+
+=cut
+
+sub LA_NOTICE { LOG_NOTICE } # normal, but significant, condition
+
+=item LA_INFO
+
+=cut
+
+sub LA_INFO { LOG_INFO } # informational message
+
+=item LA_DEBUG
+
+=cut
+
+sub LA_DEBUG { LOG_DEBUG } # debug-level message
+
+=back
+
+=cut
+
+my %log_method = (
+    syslog => undef,
+    console => LA_NOTICE,
+    callback => undef,
+    mail => undef,
+);
+
+my @maillog = ();
+
+=head1 FUNCTIONS
+
+=head2 la_set_log(%options)
+
+Set options to log dispatcher:
+
+=over 4
+
+=item syslog
+
+=item console
+
+=item callback
+
+=back
+
+=cut
+
+sub la_set_log {
+    my (%options) = @_;
+    while (my ($key, $val) = each(%options)) {
+        if ($key eq 'syslog') {
+            if ($val) {
+                my ($ident, $logopt, $facility) =  @{ ref $val ? $val : [] };
+                $ident ||= 'LATMOS::Accounts';
+                $logopt ||= 'pid';
+                $facility ||= 'LOG_USER';
+                openlog($ident, $logopt, $facility);
+                $log_method{syslog} = 1;
+            }
+            next;
+        }
+        $log_method{$key} = $val;
+    }
+    1;
+}
+
+=head2 lastmessage($level)
+
+Return the last message for C<$level> (LA_ERROR by default)
+
+=cut
+
+sub lastmessage {
+    my ($level) = @_;
+    return $lastmessages{$level || LA_ERROR};
+}
+
+=head2 la_log($level, @sprintf_args)
+
+=cut
+
+sub la_log {
+    my ($level, $msg, @args) = @_;
+    no warnings 'printf';
+
+    if (!$msg) {
+        # Wrong la_log usage
+        my @call = caller();
+        la_log(LA_WARN, 'empty message at %s:%s', $call[1], $call[2]);
+        return;
+    }
+    if ($level !~ /^\d$/) {
+        # Wrong la_log usage
+        my @call = caller();
+        la_log(LA_WARN, 'unrecognize message level %s at %s:%s', $level, $call[1], $call[2]);
+        return;
+    }
+
+    $lastmessages{$level} = sprintf($msg, map { defined($_) ? $_ : '' } @args);
+    if ($log_method{syslog}) {
+        syslog($level, $msg, @args) unless($level >= LA_DEBUG);
+    }
+    if ($ENV{'LA_DEBUG'}) {
+        my @caller = caller;
+        my $debug = ($level == LA_DEBUG) ? "$caller[0]:$caller[2] " : '';
+        warn sprintf("$debug$msg", @args) . "\n";
+    } elsif ($log_method{console} && $level <= $log_method{console}) {
+        if ($level >= LA_NOTICE && $level < LA_DEBUG) {
+            printf("$msg\n", @args);
+        } else {
+            my $debug = '';
+            if ($level == LA_DEBUG) {
+                my @caller = caller;
+                $debug = "$caller[0]:$caller[2] ";
+            }
+            warn sprintf("$debug$msg", map { defined($_) ? $_ : '' } @args) . "\n";
+        }
+    }
+    if ($log_method{callback}) {
+        $log_method{callback}->($level, $msg, @args);
+    }
+    if ($log_method{mail}) {
+        # store error to send it later, only ERROR
+        push(@maillog, sprintf($msg, @args)) if ($level <= LA_ERROR);
+    }
+    1;
+}
+
+sub _flush_mail {
+    @maillog = ();
+}
+
+sub _send_mail_log {
+    @maillog or return;
+    sendmail(
+        Subject => "LATMOS::Accounts error from $0",
+        To => $log_method{mail},
+        From => 'LATMOS-Accounts@latmos.ipsl.fr',
+        Message => join("\n", @maillog),
+    ) or la_log(LA_ERR, "Cannot sent mail: " . $Mail::Sendmail::error);
+    _flush_mail();
+}
+
+END {
+    _send_mail_log() if($log_method{mail});
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009, 2010, 2011, 2012 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Mail.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Mail.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Mail.pm	(revision 2119)
@@ -0,0 +1,147 @@
+package LATMOS::Accounts::Mail;
+
+use strict;
+use warnings;
+
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts;
+use Mail::Sendmail;
+use Template;
+use FindBin qw($Bin);
+use Sys::Hostname;
+
+=head1 NAME
+
+    LATMOS::Accounts::Mail - Send mail using Template
+
+=head1 SYNOPSIS
+
+    use LATMOS::Accounts::Mail;
+    # Prepare the mail
+    my $mail = LATMOS::Accounts::Mail->new(
+        $la,
+        'foo.mail',
+        {
+            'Subject' => 'a mail',
+        }
+    );
+    # Send it
+    $mail->process({}, { to => 'dest@domain' });
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 LATMOS::Accounts::Mail->new($la, $template, $header)
+
+Create a new C<LATMOS::Accounts::Mail> object. C<$la> is a reference to
+a C<LATMOS::Accounts> object, C<$template> is the name of the template to use,
+C<$header> is a hashref containing default mail header to use when sending mail.
+
+=cut
+
+sub new {
+    my ($class, $la, $template, $headers) = @_;
+
+    bless {
+        _la => $la,
+        template => $template,
+        headers => $headers,
+        _template => Template->new(
+            INCLUDE_PATH => [
+                ($la->val('_default_', 'templatespath')
+                    ? $la->val('_default_', 'templatespath') . '/mail'
+                    : ()),
+                "$FindBin::Bin/../templates" . '/mail',
+                '/usr/share/latmos-accounts/templates/mail',
+            ],
+            POST_CHOMP   => 1,
+            EXTENSION    => '.mail',
+        ),
+    }, $class;
+}
+
+=head2 $lamail->process($headers, $vars)
+
+Process the template as mail body and send the mail.
+
+C<$headers> is a hashref to header to use to send the mail (See
+L<Mail::Sendmail>.
+
+C<$var> is a hashref to be used by L<Template>.
+
+=cut
+
+sub process {
+    my ($self, $headers, $vars) = @_;
+
+    $vars ||= {};
+
+    my $message;
+    $self->{_template}->process($self->{template}, $vars, \$message)
+    or do {
+        la_log(LA_ERR,
+            "Cannot send mail: %s, exiting",
+            $self->{_template}->error()
+        );
+        return;;
+    };
+
+    my %mail = (
+        %{ $self->{headers} || {}},
+        %{ $headers || {}},
+        'Content-Type' => 'text/plain; charset=utf-8',
+        smtp => $self->{_la}->val('_default_', 'smtp', '127.0.0.1'),
+        'X-LATMOS-Accounts' => $LATMOS::Accounts::VERSION,
+        From => $self->{_la}->val('_default_', 'mailFrom', 'nomail@localhost'),
+        'X-Mailer' => __PACKAGE__ . ' / ' . $LATMOS::Accounts::VERSION,
+        'Auto-Submitted' => 'auto-generated',
+    );
+    {
+        my $package = __PACKAGE__;
+        $package =~ s/::/./g;
+        $mail{'Message-ID'} = join(
+            '',
+            '<', (map { unpack('H', chr(rand(255))) } (0..12)), '@',
+            hostname(), '>' );
+    }
+
+    if ($ENV{LA_NO_MAIL}) {
+        printf STDERR "mail %s to %s not sent due to \$LA_NO_MAIL env\n",
+            $mail{subject} || '', $mail{to};
+    } else {
+        if (Mail::Sendmail::sendmail(
+                %mail,
+                Message => $message,
+            )) {
+            la_log(LA_DEBUG, "Mail for id %s sent to %s",
+                $mail{'Message-ID'},
+                $mail{to},
+            );
+            return 1;
+        } else {
+            la_log(LA_ERR, "Cannot send mail: %s", $Mail::Sendmail::error);
+            return;
+        }
+    }
+}
+
+=head1 SEE ALSO
+
+L<Template>, L<Mail::Sendmail>
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.fr<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
+1;
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Maintenance.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Maintenance.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Maintenance.pm	(revision 2119)
@@ -0,0 +1,307 @@
+package LATMOS::Accounts::Maintenance;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts);
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Mail;
+use FindBin qw($Bin);
+use DateTime;
+use Date::Calc;
+use Date::Parse;
+
+=head1 NAME
+
+    LATMOS::Accounts::Maintenance
+
+=head1 FUNCTIONS
+
+=cut
+
+sub _base {
+    my ($self) = @_;
+    return $self->{_maintenance_base} if ($self->{_maintenance_base});
+    my $base = $self->SUPER::base;
+    $base->type eq 'sql' or die "This module work only with SQL base type\n";
+    return $self->{_maintenance_base} = $base
+}
+
+=head2 find_next_expire_users ($expire)
+
+Return The list of users going to expire in C<$expire> delay.
+
+=cut
+
+sub find_next_expire_users {
+    # Do not replace this code by $base->find_next_expire_users
+    # it does not exactly the same thing
+    my ($self, $expire) = @_;
+    my $base = $self->_base;
+
+    my $sth= $base->db->prepare(q{
+        select name,
+            expire as appliedexpire,
+            endcircuit,
+            expire
+            from "user"
+            where
+            expire < now() + ?::interval
+            and expire > now()
+            and expire is not null
+            and exported = True
+            order by expire
+        }
+    );
+    $sth->execute($expire || '1 month');
+    my @users;
+    while (my $res = $sth->fetchrow_hashref) {
+        my $now = DateTime->now;
+        my @expire = strptime($res->{appliedexpire});
+
+        my ($days) = Date::Calc::Delta_Days(
+            $now->year, $now->month, $now->day,
+            1900 + $expire[5], $expire[4] +1, $expire[3],
+        );
+
+        $res->{days} = $days;
+
+        $res->{obj} = $base->get_object('user', $res->{name});
+        $res->{obj}->get_attributes('locked') and next;
+        push(@users, $res);
+    }
+    @users
+}
+
+=head2 warn_next_expire_users(%options)
+
+Send a mail to user having account expiring soon
+
+C<%options are>
+
+=over 4
+
+=item users => []
+
+Warn only this users (if need)
+
+=item to
+
+Send the only to this person.
+
+=back
+
+=cut
+
+sub warn_next_expire_users {
+    my ($self, %options) = @_;
+
+    require LATMOS::Accounts::Mail;
+    my $lamail = LATMOS::Accounts::Mail->new(
+        $self,
+        'account_expire.mail',
+    );
+
+    my $MailSubject = $self->val('_default_', 'mailSubject', 'LATMOS::Accounts');
+
+    my @summary;
+    foreach my $user ($self->find_next_expire_users($options{delay})) {
+        if ($options{users} && ! grep { $_ eq $user->{name} } @{$options{users}}) {
+            next;
+        }
+
+        my %mail = (
+            From => $self->val('_default_', 'mailFrom', 'nomail@localhost'),
+            Subject => sprintf("$MailSubject: Account %s Expire in %s days", $user->{name}, $user->{days}),
+            'X-LATMOS-Reason' => 'Account expiration',
+        );
+        my ($manager, $mail) = ($user->{obj}->get_c_field('manager'),
+            $user->{obj}->get_c_field('mail'));
+        my $managermail = $manager ? $user->{obj}->base->
+            get_object('user', $manager)->get_c_field('mail') : undef;
+ 
+        # if user have no mail, mail only to manager, avoiding empty To
+        # NB: at time, for testing purpose, mail is not really sent
+        my ($to, @cc) = grep { $_ } ($mail, $managermail,
+            $self->val('_default_', 'allwayscc'));
+        if ($options{to}) {
+            $mail{to} = $options{to};
+        } else {
+            $mail{to} = $to;
+            $mail{cc} = join(', ', @cc);
+        }
+        $mail{to} or do {
+            la_log(LA_ERR, 
+                "Cannot send expiration for `%s', no mail to send to",
+                $user->{obj}->id,
+            );
+            next;
+        };
+        my $mailcc = join(', ', @cc) || '';
+        push(@summary, sprintf("%s : %s : %s\n",
+                $user->{obj}->queryformat('%{sn} %{givenName} : %{name} : %{department} : %{manager} : %{expireText} : %{endcircuit}'),
+                $to || 'Not sent, no destination',
+                ($mailcc ? $mailcc : ''),
+            )
+        );
+        my $message;
+        if ($lamail->process(\%mail, $user)) {
+            la_log(LA_NOTICE, "Expiration mail for %s (%s) sent to %s; cc %s",
+                $user->{obj}->id,
+                $user->{days},
+                $mail{to}, ($mail{cc} || ''));
+            if ($options{to}) {
+                la_log(LA_NOTICE,"\tbut sent to %s", $mail{to});
+            }
+        } 
+    }
+
+    if (@summary) {
+        if ($options{test}) {
+            print join('', @summary);
+        } else {
+            if ($self->val('_default_', 'expire_summary_to')) {
+                my $summail = LATMOS::Accounts::Mail->new(
+                    $self,
+                    \join('', @summary),
+                );
+                my %mail = (
+                );
+                if ($summail->process({
+                    Subject => "$MailSubject: account expiration summary",
+                    To => $self->val('_default_', 'expire_summary_to'),
+                })) {
+                    la_log(
+                        LA_NOTICE,
+                        "Expiration summary mail sent to %s",
+                        $self->val('_default_', 'expire_summary_to'),
+                    );
+                }
+            }
+        }
+    }
+
+    1;
+}
+
+=head2 find_expired_users ($expire)
+
+See L<LATMOS::Accounts::Base/find_expired_users>
+
+=cut
+
+sub find_expired_users {
+    my ($self, $expire) = @_;
+    $self->_base->find_expired_users($expire);
+}
+
+=head2 expired_account_reminder ( %options)
+
+Search account expired for more than C<$options{delay}> (default is 6 month)
+send mail to manager and summary to admin to aknoledge destruction.
+
+=cut
+
+sub expired_account_reminder {
+    my ($self, %options) = @_;
+    $options{delay} ||= '6 month';
+
+    my $lamail = LATMOS::Accounts::Mail->new(
+        $self,
+        'account_expired_reminder.mail',
+    );
+
+    my @users = $self->_base->find_expired_users($options{delay});
+
+    my %managers;
+
+    foreach my $user (@users) {
+        my $uobj = $self->_base->get_object('user', $user);
+        $uobj->get_attributes('unexported') and next; # can't happend
+        my $manager = $uobj->get_attributes('manager') || 'N/A';
+        push(@{$managers{$manager}{users}}, $uobj);
+
+    }
+
+    my $MailSubject = $self->val('_default_', 'mailSubject', 'LATMOS::Accounts');
+
+    unless($options{test}) {
+        foreach (keys %managers) {
+            my $oman = $self->_base->get_object('user', $_) or next; # can't happend
+            $managers{$_}{manager} = $oman;
+            my $mail = $oman->get_attributes('mail') or next;
+
+            my %mail = (
+                Subject => "$MailSubject: expired account",
+                'X-LATMOS-Reason' => 'Account destruction',
+            );
+            $mail{to} = $options{to} || $mail;
+            if ($lamail->process(\%mail, $managers{$oman->id})) {
+                la_log(LA_NOTICE,
+                    "Expired account reminder mail for %s sent to %s (cc: %s) for %s",
+                    $oman->id,
+                    $mail{to},
+                    ($mail{cc} || ''),
+                    join(', ', map { $_->id } @{$managers{$oman->id}{users}})
+                );
+            }
+        }
+    }
+    my @summary;
+    foreach my $manager (sort keys %managers) {
+        push(@summary, "\n" . (
+            $managers{$manager}{manager}
+                ? $managers{$manager}{manager}->get_attributes('displayName')
+                : $manager) . "\n");
+        foreach (@{$managers{$manager}{users}}) {
+            push(@summary, sprintf("  %s - %s (%s)\n",
+                $_->id,
+                $_->get_attributes('displayName'),
+                $_->get_attributes('expireText'),
+            ));
+        }
+    }
+
+    if (@summary) {
+        if ($options{test}) {
+            print join('', @summary);
+        } else {
+            if ($self->val('_default_', 'expire_summary_to')) {
+                my %mail = (
+                    Subject => "$MailSubject: expired account (to disable)",
+                    'X-LATMOS-Reason' => 'Account expiration',
+                    To => $self->val('_default_', 'expire_summary_to'),
+                );
+                my $summail = LATMOS::Accounts::Mail->new(
+                    $self, \join('', @summary), {}
+                );
+                if ($summail->process(\%mail)) {
+                    la_log(LA_NOTICE, "Expiration summary mail sent to %s",
+                        $self->val('_default_', 'expire_summary_to'),
+                    );
+                }
+            }
+        }
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Bases>
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009, 2010, 2011, 2012 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/SyncManager.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/SyncManager.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/SyncManager.pm	(revision 2119)
@@ -0,0 +1,293 @@
+package LATMOS::Accounts::SyncManager;
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts;
+use Config::IniFiles;
+
+=head1 NAME
+
+LATMOS::Accounts::SyncManager - Routine task manager
+
+=head1 DESCRIPTION
+
+Handle Task process run by L<la-sync-manager>
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 new($config)
+
+Instaciate object. 
+
+C<$config> is a path to an alternative configuration file to default one.
+
+=cut
+
+sub new {
+    my ($class, $config) = @_;
+
+    my $ini = Config::IniFiles->new(
+        -file => join('/', ($config || '/etc/latmos-accounts'),
+            'la-sync-manager.ini'),
+    ) or do {
+        la_log LA_ERR, "Cannot load config";
+        return;
+    };
+
+    foreach my $section (qw(basessynchro refreshexpired updatedyndata)) {
+        if ( !grep {
+            lc($ini->val($_, 'type', '')) eq $section
+            } $ini->Sections ) {
+            $ini->newval("_$section", 'type', $section);
+        }
+    }
+
+    bless {
+        ini => $ini,
+        config => $config,
+        lastrev => 0,
+    }, $class;
+}
+
+=head2 ini
+
+Return a reference to the L<Ini::Files> object handling configuration.
+
+=cut
+
+sub ini { $_[0]->{ini} }
+
+=head2 dbrev
+
+Return the current base revision
+
+=cut
+
+sub dbrev {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1);
+    my $base = $LA->base;
+    return $base->current_rev;
+}
+
+sub _load_state_ini {
+    my ($self) = @_;
+    my $la = LATMOS::Accounts->new($self->{config}, noacl => 1);
+
+    my $state_file =  $la->val('_default_', 'state_dir', '/');
+    $state_file .= '/la-sync-manager.ini';
+    la_log(LA_DEBUG, "Status file is %s", $state_file);
+    if ($state_file && ! -w $state_file) {
+        # don't exists, we have to create it
+        open(my $handle, '>', $state_file) or do {
+            la_log(LA_ERR, "Cannot open build net status file %s",
+                $state_file);
+            return;
+        };
+        print $handle "[_default_]\n";
+        close($handle);
+    }
+    return Config::IniFiles->new(
+        -file => $state_file
+    );
+}
+
+=head2 get_last_rev($module)
+
+Return the last revision the module succeed
+
+=cut
+
+sub get_last_rev {
+    my ($self, $module) = @_;
+
+    my $ini = $self->_load_state_ini or return(0, 0);
+
+    return ($ini->val($module, 'rev', 0), $ini->val($module, 'time', 0));
+}
+
+=head2 list_modules
+
+List configured task module
+
+=cut
+
+sub list_modules {
+    my ($self) = @_;
+    $self->ini->Sections;
+}
+
+
+=head2 listSortedModules
+
+List configured module ordered for running
+
+=cut
+
+sub listSortedModules {
+    my ($self) = @_;
+
+    my %modules = ();
+
+    foreach my $module ($self->list_modules) {
+
+        $self->ini->val($module, 'disable') and do {
+            la_log(LA_DEBUG, "Module $module is disable, not running it");
+            next;
+        };
+
+        $modules{$module} = 1; # default
+        my $modtype = $self->ini->val($module, 'type', $module);
+        my $task = LATMOS::Accounts::Task->new(
+            $modtype,
+            config => $self->{config},
+            name   => $module,
+            syncm  => $self,
+        )
+            or do {
+            la_log(LA_ERR, 'Cannot load module %s', $modtype);
+            next;
+        };
+
+        $modules{$module} = $task->order;
+    }
+
+    return sort { $modules{$a} <=> $modules{$b} } keys %modules;
+}
+
+=head2 process_module($module)
+
+Process C<$module>.
+
+=cut
+
+sub process_module {
+    my ($self, $module) = @_;
+
+    eval {
+        if (!$self->ini->SectionExists($module)) {
+            la_log LA_ERR, "Cannot run inexistant module %s", $module;
+            return;
+        }
+
+        my $modtype = $self->ini->val($module, 'type', $module);
+        my $task = LATMOS::Accounts::Task->new(
+            $modtype,
+            config => $self->{config},
+            name   => $module,
+            syncm  => $self,
+        )
+            or do {
+            la_log(LA_ERR, 'Cannot load module %s', $modtype);
+            return;
+        };
+        if (!$task->init) {
+            la_log(LA_ERR, 'init() failed for module %s', $module);
+            return;
+        }
+
+        my $currentrev = $self->dbrev;
+        $task->needToBeRun($self, $module) or return 1;
+
+        la_log LA_NOTICE, "Processing sync module %s (%s)", $module, $modtype;
+
+        my $res = $task->run;
+        if (!$res) {
+            my $ini = $self->_load_state_ini or return;
+            $ini->newval($module, 'error', LATMOS::Accounts::Log::lastmessage());
+            la_log LA_ERR, "Task %s did not end successfully", $module;
+            $ini->RewriteConfig;
+        } else {
+            $task->post if ($res);
+            my $ini = $self->_load_state_ini or return;
+
+            $ini->delval($module, 'error');
+            $ini->newval($module, 'rev', $currentrev);
+            my $time = time;
+            $ini->newval($module, 'time', $time);
+            $ini->SetParameterComment(
+                $module, 'time',
+                scalar(localtime(time)),
+            );
+            $ini->RewriteConfig;
+
+            la_log LA_DEBUG, "end process $module";
+        }
+        return $res;
+    };
+
+    if ($@) {
+        la_log(LA_CRIT, 'Fatal Perl Error: %s', $@);
+        return;
+    } else {
+        return 1;
+    }
+}
+
+=head2 reset_module_savepoint($module)
+
+Reset the savepoint for module C<$module> to force it to run at next
+synchronisation.
+
+=cut
+
+sub reset_module_savepoint {
+    my ($self, $module) = @_;
+    if (!$self->ini->SectionExists($module)) {
+        la_log LA_ERR, "Cannot run inexistant module %s", $module;
+        return;
+    }
+
+    my $modtype = $self->ini->val($module, 'type', $module);
+    my $task = LATMOS::Accounts::Task->new(
+        $modtype,
+        config => $self->{config},
+        name   => $module,
+        syncm  => $self,
+    )
+        or do {
+        la_log(LA_ERR, 'Cannot load module %s', $modtype);
+        return;
+    };
+    $task->init or do {
+        la_log LA_ERR, "Cannot init module %s", $_;
+        return;
+    };
+
+    my $ini = $self->_load_state_ini or return;
+
+    $ini->delval($module, 'rev');
+    $ini->newval($module, 'time', 0);
+    $ini->SetParameterComment(
+        $module, 'time',
+        'Reset to 0 the ' . scalar(localtime(time)),
+    );
+
+    $task->reset_savepoint; # Specific reset
+    $ini->RewriteConfig;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accountsi::Task>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/SynchAccess.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/SynchAccess.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/SynchAccess.pm	(revision 2119)
@@ -0,0 +1,97 @@
+package LATMOS::Accounts::SynchAccess;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::SynchAccess::base);
+use LATMOS::Accounts::SynchAccess::Objects;
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::SynchAccess
+
+=head1 FUNCTIONS
+
+=cut 
+
+=head2 get_object ($otype, $uid)
+
+Compatibility function: return a set of objects for which action must be done.
+
+=cut
+
+sub get_object {
+    my ($self, $otype, $uid) = @_;
+    my @subobj;
+    foreach ($self->bases) {
+        if ($_->is_supported_object($otype)) {
+            my $u = $_->get_object($otype, $uid) or do {
+                $_->log(LA_ERR,
+                    "Cannot get object $otype/$uid",
+                );
+                return;
+            };
+            push(@subobj, $u);
+        }
+    }
+
+    return LATMOS::Accounts::SynchAccess::Objects->new(
+        [ @subobj ]
+    );
+}
+
+=head2 get_object_ifexists ($otype, $uid)
+
+Compatibility function: return a set of objects for which action must be done.
+
+Instead get_object, if the named object does not exists in a database no error is returned
+
+=cut
+
+sub get_object_ifexists {
+    my ($self, $otype, $uid) = @_;
+    my @subobj;
+    foreach ($self->bases) {
+        $_->log(LA_DEBUG,
+            "trying to get object $otype/$uid",
+        );
+        if ($_->is_supported_object($otype)) {
+            my $u = $_->get_object($otype, $uid) or do {
+                $_->log(LA_WARN,
+                    "Cannot get object $otype/$uid",
+                );
+                next
+            };
+            push(@subobj, $u);
+        }
+    }
+
+    if (!@subobj) {
+        la_log(LA_ERR, "No object $otype/$uid found in any bases");
+        return;
+    }
+
+    return LATMOS::Accounts::SynchAccess::Objects->new(
+        [ @subobj ]
+    );
+}
+
+1;
+
+__END__
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/SynchAccess/Objects.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/SynchAccess/Objects.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/SynchAccess/Objects.pm	(revision 2119)
@@ -0,0 +1,40 @@
+package LATMOS::Accounts::SynchAccess::Objects;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::SynchAccess::base);
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::SynchAccess::Object - Fake object for sync access
+
+=head1 DESCRIPTION
+
+This module fake an object to send same actions tu multiple base. This is
+used for actions basic synchronisation process cannot handle.
+
+=cut
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>, L<LATMOS::Accounts::Bases>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/SynchAccess/base.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/SynchAccess/base.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/SynchAccess/base.pm	(revision 2119)
@@ -0,0 +1,144 @@
+package LATMOS::Accounts::SynchAccess::base;
+
+use strict;
+use warnings;
+use vars qw($AUTOLOAD);
+use Carp;
+use LATMOS::Accounts::Log;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::SynchAccess::base - Fake base object for sync access
+
+=head1 DESCRIPTION
+
+This module fake a base object to send same actions tu multiple base. This is
+used for actions basic synchronisation process cannot handle.
+
+This module contain common functions between Base and Object managment.
+
+=head1 FUNCTIONS
+
+=cut
+
+sub AUTOLOAD {
+    my ($self, @args) = @_;
+    my ($constname, $sub) = $AUTOLOAD =~ m/(.*)::([^:]+)/;
+    if ($sub =~ /^(wexported)/) {
+        $self->_no_trap($sub, @args);
+    } elsif ($sub =~ /^(check_password)$/) {
+        $self->_run_first($sub, @args);
+    } else {
+        $self->_trap_false($sub, @args);
+    }
+}
+
+=head2 new( \@bases )
+
+Create a new SynchAccess instance over given bases
+
+=cut
+
+sub new {
+    my ($class, $bases) = @_;
+    bless {
+        bases => [ @{ $bases } ],
+    }, $class;
+}
+
+=head2 bases
+
+Return lists of bases in ths instance.
+
+=cut
+
+sub bases {
+    my ($self) = @_;
+    @{ $self->{bases} || []}
+}
+
+sub _return_all {
+    my ($self, $sub, @args) = @_;
+    map { $_->$sub(@args) } $self->bases
+}
+
+sub _run_first {
+    my ($self, $sub, @args) = @_;
+    my ($fbase) = $self->bases;
+    $fbase->$sub(@args);
+}
+
+sub _no_trap {
+    my ($self, $sub, @args) = @_;
+    foreach ($self->bases) {
+        $_->$sub(@args);
+    }
+}
+
+sub _trap_false {
+    my ($self, $sub, @args) = @_;
+    my $failure = 0;
+    foreach ($self->bases) {
+        $_->$sub(@args) or do {
+		    la_log(LA_ERR, "SyncA: Erreur call $sub() for %s", $_);
+	        $failure = 1;
+        };
+    }
+    $failure ? 0 : 1;
+}
+
+sub _trap_true {
+    my ($self, $sub, @args) = @_;
+    my $failure = 0;
+    foreach ($self->bases) {
+        $_->$sub(@args) and $failure = 1;
+    }
+    $failure ? 0 : 1;
+}
+
+=head2 rename_object
+
+=cut
+
+sub rename_object {
+    my ($self, $otype, $uid, $newuid) = @_;
+    foreach ($self->bases) {
+        if ($_->is_supported_object($otype)) {
+            $_->rename_object($otype, $uid, $newuid) or do {
+                la_log(LA_ERR,
+                    "Cannot get object $otype/$uid in %s/%s",
+                    $_->type,
+                    $_->label
+                );
+                return;
+            };
+        }
+    }
+    return 1;
+}
+
+sub DESTROY {}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>, L<LATMOS::Accounts::Bases>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Synchro.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Synchro.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Synchro.pm	(revision 2119)
@@ -0,0 +1,569 @@
+package LATMOS::Accounts::Synchro;
+
+use 5.010000;
+use strict;
+use warnings;
+use base qw(Config::IniFiles);
+use LATMOS::Accounts::Bases;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils qw(exec_command);
+use Fcntl qw(:flock);
+
+=head1 NAME
+
+LATMOS::Accounts::Synchro - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+  use LATMOS::Accounts;
+  blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for LATMOS::Accounts, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+=head1 FUNCTIONS
+
+=cut
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head2 new($from, $to, %options)
+
+Create a new synchronisation where $from and $to are LATMOS::Accounts::Base
+based objects. $to can be an array ref of objects.
+
+=cut
+
+sub new {
+    my ($class, $from, $to, %options) = @_;
+
+    my $state_file = $options{state_dir}
+        ? $options{state_dir} . '/synchronisation.ini'
+        : undef;
+    if ($state_file && ! -w $state_file) {
+        # don't exists, we have to create it
+        open(my $handle, '>', $state_file) or do {
+            la_log(LA_ERR, "Cannot open status file %s", $state_file);
+            return;
+        };
+        print $handle "[_default_]\n";
+        close($handle);
+    }
+
+    my $self = Config::IniFiles->new(
+        $state_file
+        ? (-file => $state_file)
+        : (),
+    );
+
+    if ($state_file && !$self->GetFileName) {
+        $self->SetFileName($state_file);
+    }
+    
+    $self->{from} = $from or do {
+        la_log(LA_ERR, "No database source");
+        return;
+    };
+    $self->{options} = { %options };
+
+    # allow ref and array ref of, eg
+    # to = $foo and $to = [ $foo, $bar ]
+    foreach (ref($to) eq 'ARRAY' ? @{ $to || []} : ($to)) {
+        push(@{$self->{to}}, $_);
+    }
+    bless($self, $class)
+}
+
+=head2 name
+
+Return the name of this synchronisation
+
+=cut
+
+sub name {
+    $_[0]->{options}{name}
+}
+
+=head2 from
+
+Return the base object source for this synchronisation
+
+=cut
+
+sub from {
+    my ($self) = @_;
+    return $self->{from}
+}
+
+=head2 to
+
+Return the list of base destination for this synchronisation
+
+=cut
+
+sub to {
+    my ($self) = @_;
+    return @{$self->{to} || []};
+}
+
+=head2 load_dest
+
+Try to loaded all base, return the count of filtrered base which cannot
+be loaded
+
+=cut
+
+sub load_dest {
+    my ($self) = @_;
+    my @loaded;
+    my $unloaded = 0;
+    foreach ($self->to) {
+        if($_->load) {
+            push(@loaded, $_);
+        } else {
+            $unloaded++;
+            warn "Cannot load $_";
+        }
+    }
+    $self->{to} = \@loaded;
+    return $unloaded;
+}
+
+=head2 enter_synch_mode
+
+Configure base for synchronisation
+
+=cut
+
+sub enter_synch_mode {
+    my ($self) = @_;
+    $self->from->load or return;
+    # if any cannot be loaded, return,
+    # TODO we need a way to force if some still can be sync
+    $self->load_dest and return;
+    my %state = ();
+    $state{$self->from->label} = $self->from->wexported(
+        $self->{options}{unexported} ? 1 : 0
+    );
+    foreach ($self->to) {
+        $state{$_->label} = $_->wexported(1);
+    }
+    la_log(LA_DEBUG, "Entering synch mode, old state: %s", join(', ', map {
+            "$_ => $state{$_}" } sort keys %state));
+    %state
+}
+
+=head2 leave_synch_mode (%state)
+
+Retore base to previous state
+
+=cut
+
+sub leave_synch_mode {
+    my ($self, %state) = @_;
+    la_log(LA_DEBUG, "Leaving synch mode");
+    $self->from->wexported($state{$self->from->label});
+    foreach my $base (grep { $_ } $self->to) {
+        $base->wexported($state{$base->label});
+    }
+}
+
+=head2 lock
+
+Create a lock to denied another synchronisation to run
+
+=cut
+
+sub lock {
+    my ($self) = @_;
+
+    $self->{lock}{handle} and return 1;
+    la_log(LA_DEBUG, "Trying to lock (pid $$)");
+    if ($self->{options}{state_dir}) {
+        my $lockfile = $self->{options}{state_dir} . '/synclock';
+        open(my $handle, '>>', $lockfile) or return;
+        flock($handle, LOCK_EX);
+        $self->{lock}{handle} = $handle;
+        $self->{lock}{filename} = $lockfile;
+        la_log(LA_DEBUG, "lock done (pid $$)");
+        return 1;
+    } else { return 1 }
+}
+
+=head2 unlock
+
+Remove lock
+
+=cut
+
+sub unlock {
+    my ($self) = @_;
+    if (my $handle = $self->{lock}{handle}) {
+        close($handle);
+        delete($self->{lock}{handle});
+        unlink($self->{lock}{filename});
+        delete($self->{lock}{filename});
+        return 1;
+    }
+    return;
+}
+
+=head2 sync_object ($otype, $uid, %options)
+
+Synchronise object type C<$otype> named C<$uid>
+
+=cut
+
+sub sync_object {
+    my ($self, $otype, $uid, %options) = @_;
+
+    $self->lock or return;
+
+    my %state = $self->enter_synch_mode;
+    
+    my $res = $self->_sync_object($otype, $uid, %options);
+
+    $self->leave_synch_mode(%state);
+
+    $self->unlock;
+
+    $res;
+}
+
+sub _sync_object {
+    my ($self, $otype, $uid, %options) = @_;
+    foreach ($self->to) {
+        my $res = $_->sync_object_from($self->from, $otype, $uid, %options);
+        if (defined $res) {
+            la_log(LA_NOTICE, $_->label . " $uid ($otype) $res") if ($res);
+            return 1;
+        } else {
+            la_log(LA_ERR, "error synching $uid ($otype) to " . $_->label);
+            return;
+        }
+    }
+}
+
+=head2 process
+
+Run the syncronisation
+
+=cut
+
+sub process {
+    my ($self, %options) = @_;
+
+    $self->lock or return;
+    
+    if (!(my $res = $self->run_pre_synchro({}))) {
+        la_log(LA_ERR, "Pre synchro script failed, aborting");
+        $self->unlock;
+        return;
+    }
+
+    my %state = $self->enter_synch_mode;
+
+    # tracking current base revision:
+    $self->{current_rev} = $self->from->current_rev;
+
+    my %desterror;
+    my $updated = 0;
+
+    # We do base one by one
+
+    foreach my $destbase ($self->to) {
+        my %objlist;
+        foreach my $otype ($self->from->list_supported_objects) {
+            $destbase->is_supported_object($otype) or next;
+
+            my %existings;
+            my %filtering;
+
+            $existings{$otype} = { map { $_ => 1 }
+                $self->from->listRealObjects($otype) };
+
+            # Is there a filter to apply:
+            my $filtername = 'filter.' . $destbase->label . '.' . $otype;
+            if (my $filter = $self->{options}->{$filtername}) {
+                la_log(LA_DEBUG, "Found %s param, using it: %s", $filtername, $filter);
+                $filtering{$otype} = { map { $_ => 1 }
+                    $self->from->search_objects($otype, $filter, 'oalias=NULL') };
+            } else {
+                $filtering{$otype} = $existings{$otype};
+            }
+
+
+            # deleting non existing object in dest:
+
+            # Sync: noDelete.Base = yes          mean no delete
+            #       noDelete.Base.Otype = yes    mean no delete for this object
+            if ($self->{options}->{'noDelete'} ||
+                $self->{options}->{'noDelete.' . $destbase->label} ||
+                $self->{options}->{'noDelete.' . $destbase->label . '.' . $otype}) {
+                la_log(LA_INFO, 'Not deleting object type \'%s` from base \'%s` because %s is set',
+                    $otype,
+                    $destbase->label,
+                    $self->{options}->{'noDelete.' . $destbase->label . '.' . $otype}
+                        ? 'noDelete.' . $destbase->label . '.' . $otype
+                        : $self->{options}->{'noDelete.' . $destbase->label}
+                            ? 'noDelete.' . $destbase->label
+                            : 'noDelete'
+                );
+            } else {
+
+            my $deletefiltered = 'deletefiltered.' . $destbase->label . '.' . $otype;
+
+            foreach ($destbase->listRealObjects($otype)) {
+
+                if ($filtering{$otype}{$_}) {
+                    # the object must exists
+                    next;
+                } elsif ($existings{$otype}{$_}) {
+                    # object exists but is filtered
+                    if (!$self->{options}->{$deletefiltered}) {
+                        next;
+                    }
+                }
+
+                if (my $res = $destbase->sync_object_from($self->from,
+                        $otype, $_, %options)) {
+                    la_log(LA_NOTICE, "%s::%s::%s => %s %s",
+                        $self->from->label, $otype, $_, $destbase->label, $res,
+                    );
+                    if ($destbase->is_transactionnal) {
+                        $destbase->commit;
+                    }
+                    $updated = 1;
+                } else {
+                    if ($destbase->is_transactionnal) {
+                        $destbase->rollback;
+                    }
+                }
+            }
+            } # noDelete.Base
+
+
+            # Finding object to synchronize:
+            @{$objlist{$otype}} = grep { $filtering{$otype}{$_} } $self->from->list_objects_from_rev(
+                $otype,
+                $self->val($self->from->label, $destbase->label, 0),
+            );
+        }
+
+        my %objectok;
+        foreach my $pass (1, 0) {
+            foreach my $otype ($destbase->ordered_objects) {
+                exists($objlist{$otype}) or next;
+                foreach (@{$objlist{$otype} || []}) {
+
+                    # If first synchro is not ok, don't retry to sync the object
+                    if ((!$pass) && (!$objectok{$otype}{$_})) {
+                        next;
+                    }
+
+                    my $res = $destbase->sync_object_from($self->from, $otype, $_,
+                        %options, firstpass => $pass);
+                    if (defined $res) {
+                        if ($res) {
+                            la_log(LA_NOTICE, "%s::%s::%s => %s %s",
+                                $self->from->label, $otype, $_,
+                                $destbase->label, $res,
+                            );
+                            if ($destbase->is_transactionnal) {
+                                $destbase->commit;
+                            }
+                            $updated = 1;
+                            $objectok{$otype}{$_} = 1;
+                        }
+                    } else {
+                        la_log(LA_ERR, "Cannot synch %s::%s::%s => %s",
+                            $self->from->label, $otype, $_,
+                            $destbase->label,
+                        );
+                        $desterror{$destbase->label} = 1;
+                        if ($destbase->is_transactionnal) {
+                            $destbase->rollback;
+                        }
+                    }
+                }
+            }
+        }
+        if (!$desterror{$destbase->label}) {
+            $destbase->commit if(!$destbase->is_transactionnal);
+            $self->newval($self->from->label, $destbase->label, $self->{current_rev});
+            if(!($self->{options}{nocreate} || $self->{options}{test})) {
+                $self->write_status;
+                la_log(LA_NOTICE,
+                    "Update synch. status to %s for base %s to %s",
+                    $self->{current_rev} || '(none)', $self->from->label, $destbase->label
+                ); 
+            }
+        }
+    }
+
+    $self->leave_synch_mode(%state);
+    my $res = $self->run_post_synchro(
+        {
+            UPDATED => $updated || undef,
+        }
+    );
+    if ($res) { 
+    } else {
+        la_log(LA_ERROR, "Post synchronization script failed: %s", $res);
+    }
+
+    $self->unlock;
+
+    if (!$res) { # postscript failure
+        return;
+    } elsif (grep { $desterror{$_} } keys %desterror) {
+        # There were errors :\
+        return;
+    } else {
+        return 1;
+    }
+}
+
+=head2 write_status
+
+Write savepoint file
+
+=cut
+
+sub write_status {
+    my ($self) = @_;
+    if (my $file = $self->GetFileName) {
+        open(my $handle, '>', $file) or do {
+            la_log(LA_ERR, "Cannot open status file %s for writing: %s",
+                $file, $!);
+            return;
+        };
+        my $oldfh = select($handle);
+        $self->OutputConfig();
+        select($oldfh);
+        close($handle); 
+        return 1;
+    }
+
+    return 0;
+}
+
+=head2 reset_savepoint
+
+Reset savepoint in status file to force full synchronisation
+
+=cut
+
+sub reset_savepoint {
+    my ($self) = @_;
+    foreach my $destbase ($self->to) {
+            # don't register savepoint on error
+        $self->newval($self->from->label, $destbase->label, 0);
+    }
+    $self->write_status;
+}
+
+=head2 run_pre_synchro
+
+Run task done before synchronisation
+
+=cut
+
+sub run_pre_synchro {
+    my ($self, $env) = @_;
+
+    $env ||= {};
+    $env->{HOOK_TYPE} = 'PRE';
+
+    foreach my $base ($self->to) {
+        if ($base->config('presynchro')) {
+            la_log LA_DEBUG, "Executing base pre synchro `%s' for %s",
+                $base->config('presynchro'), $base->label;
+            exec_command(
+                $base->config('presynchro'),
+                {
+                    BASE => $base->label,
+                    BASETYPE => $base->type,
+                    %{ $env },
+                }
+            );
+        }
+    }
+
+    $self->{options}{pre} or return 1;
+
+    la_log(LA_DEBUG, "Running post synchro `%s'", $self->{options}{pre});
+
+    exec_command($self->{options}{post}, $env);
+}
+
+=head2 run_post_synchro
+
+Run task done after synchronisation
+
+=cut
+
+sub run_post_synchro {
+    my ($self, $env) = @_;
+    
+    $env ||= {};
+    $env->{HOOK_TYPE} = 'PRE';
+
+    foreach my $base ($self->to) {
+        if ($base->config('postsynchro')) {
+            la_log LA_DEBUG, "Executing base post synchro `%s' for %s",
+                $base->config('postsynchro'), $base->label;
+            exec_command(
+                $base->config('postsynchro'),
+                {
+                    BASE => $base->label,
+                    BASETYPE => $base->type,
+                    %{ $env },
+                }
+            );
+        }
+    }
+
+    $self->{options}{post} or return 1;
+
+    la_log(LA_DEBUG, "Running post synchro `%s'", $self->{options}{post});
+    
+    exec_command($self->{options}{post}, $env);
+}
+
+
+1;
+
+__END__
+# Below is stub documentation for your module. You'd better edit it!
+
+=head1 SEE ALSO
+
+Mention other useful documentation such as the documentation of
+related modules or operating system documentation (such as man pages
+in UNIX), or any relevant external documentation such as RFCs or
+standards.
+
+If you have a mailing list set up for your module, mention it here.
+
+If you have a web site set up for your module, mention it here.
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmosipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task.pm	(revision 2119)
@@ -0,0 +1,199 @@
+package LATMOS::Accounts::Task;
+
+use strict;
+use warnings;
+use LATMOS::Accounts::Log;
+
+=head1 NAME
+
+    LATMOS::Accounts::Task
+
+=head1 DESCRIPTION
+
+Parent class for regular task run by L<la-sync-manager>
+
+=head1 FUNCTIONS
+
+=head2 new($module, %options)
+
+Instanciate a new task of type C<$module>
+
+The module must provide a C<_new()> function.
+
+=cut
+
+sub new {
+    my ($class, $module, %options) = @_;
+    my $pclass = ucfirst(lc($module));
+    my $me = __PACKAGE__;
+    eval "require ${me}::$pclass;";
+    if ($@) { return } # error message ?
+    return "${me}::$pclass"->_new(%options);
+}
+
+sub _new {
+    my ($class, %options) = @_;
+    bless { %options }, $class;
+}
+
+=head2 config($value, $default)
+
+Return config value for the module
+
+=cut
+
+sub config {
+    my ($self, $value, $default) = @_;
+
+    return $self->{syncm}->ini->val($self->{name}, $value, $default)
+}
+
+=head2 order
+
+Return the priority for the module: 0, 1 or 2 (0 = first)
+
+=cut
+
+sub order { 1 }
+
+=head2 init
+
+Call at task startup, can be overload
+
+=cut
+
+sub init {
+    return 1;
+}
+
+=head2 needupd($baserev, $syncm)
+
+This function is to call to check if the module have to run or not.
+
+C<$baserev> is the current base revision en C<$syncm> the revision when
+la-sync-manager was run.
+
+By default return true only if base has changed, overload for different
+behavior.
+
+=cut
+
+sub needupd {
+    my ($self, $lastrev, $currentrev) = @_;
+
+    la_log LA_DEBUG, "Comparing db: %d <=> %d", $currentrev, $lastrev;
+    if ($currentrev > $lastrev) {
+        return 1;
+    } else {
+        la_log LA_DEBUG, "No change in DB, doing nothing";
+        return;
+    }
+}
+
+=head2 runDelay
+
+Return the minumum of second to wait to have the module running again.
+
+Return 0 but can be override by Task module.
+
+=cut
+
+sub runDelay { 0; }
+
+=head2 waitDelay
+
+Return the number of second to wait before next run even there is a change in database.
+
+Return 0 but can be override by Task module.
+
+=cut
+
+sub waitDelay { 0; }
+
+=head2 needToBeRun
+
+Return TRUE is the module has to be run or not.
+
+=cut
+
+sub needToBeRun {
+    my ($self, $Sync, $module) = @_;
+
+    my ($lastrev, $lastRunTime) = $Sync->get_last_rev($module);
+
+    my $currentrev = $Sync->dbrev;
+    my $currentTime = time;
+    
+    if ($currentTime < $lastRunTime + $self->waitDelay) {
+        la_log LA_DEBUG, "Too early to run this module now";
+        return 0;
+    }
+
+    if ($self->needupd($lastrev, $currentrev, $Sync)) {
+        return 1;
+    } else {
+        la_log LA_DEBUG, "No change on main base, cheking delay";
+    }
+
+
+    if (my $delay = $self->runDelay) {
+        if ($currentTime >= $lastRunTime + $self->runDelay) {
+            return 1;
+        } else {
+            la_log LA_DEBUG, "Too short delay to run self, waiting";
+        }
+    }
+
+    return 0;
+}
+
+=head2 run
+
+Must be provided by module, do the desired work.
+
+=cut
+
+sub run {
+    return 1;
+}
+
+=head2 post
+
+Call after C<run()>
+
+=cut
+
+sub post {
+}
+
+=head2 reset_savepoint
+
+Reset the savepoint to 0 to force the module to be run again
+
+=cut
+
+sub reset_savepoint {
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::SyncManager>
+
+=head1 AUTHOR
+
+Thauvin Olivier, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009, 2010, 2011, 2012 by Thauvin Olivier
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Basessynchro.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Basessynchro.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Basessynchro.pm	(revision 2119)
@@ -0,0 +1,89 @@
+package LATMOS::Accounts::Task::Basessynchro;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts;
+use LATMOS::Accounts::Log;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Basessynchro - Synchronize bases
+
+=head1 DESCRIPTION
+
+This module is responsible of the copy of object from main base to others
+
+=cut
+
+sub order { 2 }
+
+sub run {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1) or do {
+        la_log LA_ERR, "Cannot instantiate LA";
+        return 1;
+    };
+
+    if (my $mail = $LA->val($self->{syncname} || $LA->default_synchro_name, 'maillog')) {
+        la_set_log(mail => $mail);
+    }
+
+    my $sync = $LA->create_synchro(
+        $self->{syncname} || $LA->default_synchro_name,
+        test     => $self->{test},
+    ) or do {
+       la_log LA_ERR, "cannot create sync object";
+       return;
+    };
+
+    $sync->load_dest and do {
+        la_log LA_ERR, "Cannot load destination";
+        return;
+    };
+
+    $sync->process() or do {
+        la_log LA_ERR, "Sync has failed\n";
+        return;
+    };
+
+    return 1;
+}
+
+sub reset_savepoint {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1) or do {
+        la_log LA_ERR, "Cannot instantiate LA";
+        return;
+    };
+    my $sync = $LA->create_synchro(
+        $self->{syncname} || $LA->default_synchro_name,
+        test     => $self->{test},
+    ) or do {
+       la_log LA_ERR, "cannot create sync object";
+       return;
+    };
+
+
+    $sync->reset_savepoint
+}
+
+1;
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>, L<LATMOS::Accounts::Bases>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Buildlistes.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Buildlistes.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Buildlistes.pm	(revision 2119)
@@ -0,0 +1,188 @@
+package LATMOS::Accounts::Task::Buildlistes;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts;
+use Config::IniFiles;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Buildlistes - Task to generate list of mail address
+usable by mail robot such as mailman.
+
+=head1 DESCRITPTION
+
+This module is designed to automatically build mailing list members file. The
+configuration is handle by F<la-sync-list.ini> file.
+
+=cut
+
+sub runDelay { 60 * 60 } # 1 hour
+
+sub init {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1);
+    my $labase = $self->{base} ? $LA->base($self->{base}) : $LA->base;
+    $labase && $labase->load or die "Cannot load base";
+
+    my $listconfig = $self->{listconfig} || join('/', $LA->_configdir, 'la-sync-list.ini');
+    my $listcfg = Config::IniFiles->new(
+        -file => $listconfig,
+        -default => '_default_',
+    ) or do {
+        la_log LA_ERR, "Cannot open list config file $listconfig";
+        return;
+    };
+
+    $self->{_la} = $LA;
+    $self->{_base} = $labase;
+    $self->{_listcfg} = $listcfg;
+
+    1;
+}
+
+sub run {
+    my ($self) = @_;
+    my $listcfg = $self->{_listcfg};
+    my $labase = $self->{_base};
+
+    my %cache;
+    if (my $cmd = $listcfg->val('_default_', 'pre')) {
+        exec_command(
+            $cmd,
+            {
+                DIRECTORY => $listcfg->val('_default_', 'destdir'),
+                HOOK_TYPE => 'PRE',
+            },
+        );
+    } 
+
+    foreach my $list ($listcfg->Sections) {
+        la_log LA_DEBUG, "Start to process %s", $list;
+        my %content = ();
+        $list eq '_default_' and next;
+        $listcfg->val($list, 'ignore') and do {
+            la_log LA_DEBUG, "list %s taggued 'ignored'", $list;
+            next;
+        };
+        my $fmt = $listcfg->val($list, 'fmt', '%{mail}');
+        my $otype = $listcfg->val($list, 'objects', 'user');
+        foreach (grep { $_ } $listcfg->val($list, 'addtolist')) {
+            $content{$_} = 1;
+        }
+        my %ids;
+        # finding /^filter/ as search results
+        # adding to list results
+        foreach my $param ($listcfg->Parameters($list)) {
+            $param =~ /^filter/ or next;
+
+            foreach my $id (sort $labase->search_objects(
+                    $listcfg->val($list, 'objects', 'user'),
+                    $listcfg->val($list, $param),)) {
+                $ids{$id} = 1;
+            }
+        }
+        # finding /^excludefilter/ as search results
+        # deleting from list results
+        my %done;
+        foreach my $param ($listcfg->Parameters($list),
+                           $listcfg->Parameters('_default_')) {
+            $done{$param} and next;
+            $done{$param} = 1;
+            $param =~ /^excludefilter/ or next;
+            $listcfg->val($list, $param) or next;
+
+            foreach my $id (sort $labase->search_objects(
+                    $listcfg->val($list, 'objects', 'user'),
+                    $listcfg->val($list, $param),)) {
+                delete($ids{$id});
+            }
+        }
+        foreach my $id (sort keys %ids) {
+            if (!$cache{$otype}{$fmt}{$id}) {
+                my $obj = $labase->get_object(
+                    $otype,
+                    $id,
+                );
+
+                $cache{$otype}{$fmt}{$id} = $obj->queryformat($fmt);
+            }
+            $content{ $cache{$otype}{$fmt}{$id} } = 1;
+        }
+        # No destdir, no cmd, will do nothing...
+        if (my $destdir = $listcfg->val('_default_', 'destdir')) {
+            if (open(my $handle, '>', "$destdir/$list")) {
+                foreach (sort keys %content) {
+                    print $handle $_ ."\n";
+                }
+                close($handle);
+                la_log LA_NOTICE, "%s written", "$destdir/$list";
+                if (my $cmd = $listcfg->val('_default_', 'post_file')) {
+                    exec_command(
+                        $cmd,
+                        {
+                            DIRECTORY => $destdir,
+                            OUTPUT_FILE => $list,
+                            HOOK_TYPE => 'POSTFILE',
+                        },
+                    );
+                } 
+            } else {
+                la_log LA_ERR, "Can't open %s: %s", "$destdir/$list", $!;
+            }
+        }
+        # compat
+        if (my $cmd = $listcfg->val($list, 'cmd')) {
+            $cmd =~ s/%%/$list/g;
+            if (open(my $handle, '|' . $cmd)) {
+                foreach (sort keys %content) {
+                    print $handle $_ ."\n";
+                }
+                close($handle);
+                if ($?) {
+                    la_log(LA_ERR, "Command `%s' exit with status %d", $cmd, $?);
+                } else {
+                    la_log(LA_NOTICE, "Command `%s' done", $cmd);
+                }
+            } else {
+                la_log LA_ERR, "Can run command `%s': %s", $cmd, $!;
+            } 
+        }
+    }
+    if (my $cmd = $listcfg->val('_default_', 'post')) {
+        exec_command(
+            $cmd,
+            {
+                DIRECTORY => $listcfg->val('_default_', 'destdir'),
+                HOOK_TYPE => 'POST',
+            },
+        );
+    } 
+
+    1;
+}
+
+1;
+
+=head1 SEE ALSO
+
+Configuraiton file: L<la-sync-list.ini>
+
+L<LATMOS::Accounts::Task>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Buildnet.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Buildnet.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Buildnet.pm	(revision 2119)
@@ -0,0 +1,1037 @@
+package LATMOS::Accounts::Task::Buildnet;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+use FindBin qw($Bin);
+use POSIX qw(strftime);
+use Net::IP;
+use File::Path;
+use File::Temp qw(tempfile);
+use Net::IPv4Addr;
+use Net::IPv6Addr;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Buildnet - Task to generate network configuration files
+
+=head1 DESCRIPTION
+
+This contains functions to generate network config file from C<Netzone> and
+C<Nethost> object.
+
+These config file can be:
+
+=over 4
+
+=item DNS zone files
+
+A standard DNS zone generated from a header and entries found in bases
+
+=item DNS reverse zone files
+
+A reverse DNS zone genarated from a header and entries found in bases
+
+=item A DHCP host list
+
+A file well formated host list to be included in dhcpd config file.
+
+=back
+
+=cut
+
+sub order { 2 }
+
+# Always try because depend also on files:
+
+sub runDelay { 10 * 60 }
+
+sub init {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1);
+
+    $self->{_base} = $LA->base();
+    $self->{_base} && $self->{_base} or die "Cannot load base";
+    $self->{_base}->type eq 'sql' or die "This module work only with SQL base type\n";
+    $self->{_la} = $LA;
+}
+
+sub run {
+    my ($self) = @_;
+
+    if (my $cmd = $self->_la->val('_network_', 'pre')) {
+        exec_command(
+            $cmd,
+            {
+                TEMPLATE_DIR => $self->_la->val('_network_', 'template_dir', ''),
+                OUTPUT_DIR => $self->_la->val('_network_', 'output_dir', ''),
+                DIRECTORY => $self->_la->val('_network_', 'output_dir', ''),
+                HOOK_TYPE => 'PRE',
+            },
+        );
+    }
+
+    foreach my $zone ($self->_base->search_objects('netzone', 'exported=*')) {
+        my $ozone = $self->_base->get_object('netzone', $zone)
+            or next;
+        # check file need regeneration:
+        $self->_check_zone_need_update($ozone) or do {
+            la_log(LA_DEBUG, "No need to rebuild %s", $ozone->id);
+            next;
+        };
+        $self->gen_zone($zone) or do {
+            $self->_base->rollback;
+            return;
+        };
+        $self->_base->commit;
+    }
+
+    if (my $cmd = $self->_la->val('_network_', 'post')) {
+        exec_command(
+            $cmd,
+            {
+                TEMPLATE_DIR => $self->_la->val('_network_', 'template_dir', ''),
+                OUTPUT_DIR => $self->_la->val('_network_', 'output_dir', ''),
+                DIRECTORY => $self->_la->val('_network_', 'output_dir', ''),
+                HOOK_TYPE => 'POST',
+            },
+        );
+    }
+
+    1;
+}
+
+sub _la {
+    $_[0]->{_la}
+}
+
+sub _base {
+    my ($self) = @_;
+    return $self->{_base} if ($self->{_base});
+    my $base = $self->SUPER::base;
+    return $self->{_base} = $base
+}
+
+sub _bnet_state {
+    my ($self) = @_;
+    return $self->{_bnet_state} if ($self->{_bnet_state});
+    # where trace goes:
+    my $state_file =  $self->_la->val('_default_', 'state_dir', '/');
+    $state_file .= '/buildnet_state.ini';
+    la_log(LA_DEBUG, "Status file is %s", $state_file);
+    if ($state_file && ! -w $state_file) {
+        # don't exists, we have to create it
+        open(my $handle, '>', $state_file) or do {
+            la_log(LA_ERR, "Cannot open build net status file %s",
+                $state_file);
+            return;
+        };
+        print $handle "[_default_]\n";
+        close($handle);
+    }
+    $self->{_bnet_state} = Config::IniFiles->new(
+        -file => $state_file
+    );
+}
+
+sub _write_state_file {
+    la_log(LA_DEBUG, "Writting status file");
+    $_[0]->_bnet_state->RewriteConfig;
+}
+
+sub _template_file {
+    my ($self, $ozone) = @_;
+
+    my $template =  join('/', grep { $_ } $self->_la->val('_network_', 'template_dir'),
+        $ozone->get_attributes('templateD'));
+    la_log(LA_DEBUG, "Template for %s is %s", $ozone->id, $template);
+    $template;
+}
+
+sub _output_file {
+    my ($self, $ozone) = @_;
+
+    my $path = join(
+        '/',
+        $self->_la->val('_network_', 'output_dir',
+            ($self->_la->val('_default_', 'state_dir'), $ozone->get_attributes('type'))
+        )
+    );
+
+    if (! -d $path) {
+        la_log(LA_INFO, 'Creating directory %s', $path);
+        mkpath($path) or return;
+    }
+    my $output = join('/', $path, $ozone->get_attributes('outputD'));
+    la_log(LA_DEBUG, 'output file for %s is %s', $ozone->id, $output);
+    $output;
+}
+
+=head2 get_zone_rev ($zone)
+
+Return next zone revision (DNS).
+
+This revision is formated from date + incremental serial number. If day change,
+serial start to 1. If serial goes goes over 99, head build from date is
+increment.
+
+The code ensure returned number is always highter that current one.
+
+=cut
+
+sub get_zone_rev {
+    my ($self, $ozone) = @_;
+    my $date = strftime('%Y%m%d01', localtime);
+    my $oldrev = $ozone->get_attributes('zoneRevision') || 0;
+    my $rev;
+    if ($oldrev >= $date) {
+        # same date, increment subrev
+        $rev = $oldrev + 1;
+    } else {
+        # date has changed, subrev is 1
+        $rev = $date;
+    }
+    la_log(LA_DEBUG, 'new dns revision for %s is %s', $ozone->id, $rev);
+    $ozone->set_c_fields(zoneRevision => $rev) or do {
+        return;
+    };
+    $rev
+}
+
+
+sub _check_zone_need_update {
+    my ($self, $ozone) = @_;
+
+    # If env var is set, do it anyway
+    if ($ENV{LA_BNET_FORCE}) { return 1 }
+
+    if ($ozone->get_attributes('rev') >
+        $self->_bnet_state->val($ozone->id, 'dbrev', 0)) {
+        return 1;
+    }
+
+    return 1 if (! -f $self->_output_file($ozone));
+
+
+    if ($ozone->get_attributes('templateD')) {
+        my $template = $self->_template_file($ozone);
+        my $output = $self->_output_file($ozone);
+        my @tstat = stat($template) or return;
+        my @ostat = stat($output);
+        if (($ostat[9] || 0) <= ($tstat[9] || 0)) {
+            return 1;
+        }
+    }
+
+    return;
+}
+
+sub _set_last_build {
+    my ($self, $ozone) = @_;
+
+    my $lctime = scalar(localtime);
+    la_log(LA_DEBUG, 'Update last build for zone %s (%s)', $ozone->id, $lctime);
+    $ozone->set_c_fields('lastBuild' => $lctime);
+}
+
+sub _pre_zone {
+    my ($self, $ozone) = @_;
+
+    if (!$ozone->get_attributes('templateD')) {
+        la_log(LA_ERR, "No template file for zone %s, aborting", $ozone->id);
+        return;
+    }
+
+    my $textzone = $self->_comment_zone($ozone);
+    my $tzone = $self->_read_template($ozone);
+    if (defined($tzone)) {
+        $textzone .= $tzone;
+    } else {
+        return;
+    }
+
+    $self->_set_last_build($ozone);
+
+    return $textzone;
+}
+
+=head2 gen_zone($zone, $header)
+
+Generate zone file C<$zone> with header C<$header>
+
+=cut
+
+sub gen_zone {
+    my ($self, $zone) = @_;
+
+    my $timeS = time;
+    my $ozone = $self->_base->get_object('netzone', $zone)
+        or return;
+
+    la_log(LA_INFO, "Start building zone %s (%s)", $zone,
+        $ozone->get_attributes('type'));
+
+    my $header = $self->_pre_zone($ozone);
+
+    my $type = $ozone->get_attributes('type');
+    my $res =
+        $type eq 'dns'     ? $self->_gen_dns_zone($ozone, $header) :
+        $type eq 'reverse' ? $self->_gen_reverse_zone($ozone, $header) :
+        $type eq 'dhcp'    ? $self->_gen_dhcp_zone($ozone, $header) :
+        $type eq 'puppet'  ? $self->_gen_puppet_zone($ozone, $header) :
+        $type eq 'radius'  ? $self->_gen_radius_zone($ozone, $header) :
+        undef;
+
+    if (!defined($res)) {
+    }
+
+    my $textzone =
+        $header .
+        "\n" .
+        $self->_comment($ozone, "Comming from database:\n") .
+        $res .
+        $self->_comment($ozone, "End of data from database\n");
+
+
+    if ($type =~ /^(dns|reverse)$/) {
+        if (!$self->_checkzone_output($ozone, $textzone)) {
+            la_log(LA_ERR, "Output of DNS zone %s not ok, not updating this zone",
+                $ozone->id);
+            return;
+        }
+    }
+
+    if (open(my $handle, '>', $self->_output_file($ozone))) {
+        print $handle $textzone;
+        close($handle);
+        la_log(LA_INFO,
+            "zone %s written into %s (%d second)",
+            $ozone->id,
+            $self->_output_file($ozone),
+            time - $timeS,
+        );
+    } else {
+       la_log(LA_ERR, "Can't open output file for zone %s", $ozone->id);
+       return;
+    }
+
+    if (my $cmd = $self->_la->val('_network_', 'post_file',
+            $self->_la->val('_network_', 'post_zone'))) {
+        exec_command(
+            $cmd,
+            {
+                TEMPLATE_DIR => $self->_la->val('_network_', 'template_dir', ''),
+                OUTPUT_DIR => $self->_la->val('_network_', 'output_dir', ''),
+                DIRECTORY => $self->_la->val('_network_', 'output_dir', ''),
+                TEMPLATE_FILE => $ozone->get_attributes('templateD'),
+                OUTPUT_FILE => $ozone->get_attributes('outputD'),
+                HOOK_TYPE => 'POSTFILE',
+            },
+        );
+    }
+
+    $self->_bnet_state->newval($ozone->id, 'dbrev',
+        $ozone->get_attributes('rev'));
+    la_log LA_DEBUG, "Zone rev build point is %d for %s",
+    $ozone->get_attributes('rev'),
+    $ozone->id;
+    $self->_bnet_state->SetParameterComment(
+        $ozone->id, 'dbrev',
+        scalar(localtime));
+    $self->_write_state_file;
+
+    1;
+}
+
+sub _checkzone_output {
+    my ($self, $ozone, $output) = @_;
+
+    if (!$self->_la->val('_network_', 'checkzone')) {
+        return 1;
+    }
+
+    my ($fh, $filename) = tempfile();
+
+    print $fh $output;
+    close($fh);
+
+    my $named_checkzone = $self->_la->val('_network_', 'named-checkzone',
+        '/usr/sbin/named-checkzone');
+
+    my $msg;
+    my $res = exec_command(sprintf(
+            "%s -k fail '%s' '%s'",
+            $named_checkzone,
+            $ozone->id,
+            $filename,
+        ), undef, $msg);
+    if (!$res) {
+        la_log(LA_ERR, "Error on zone %s: ", $ozone->id);
+        la_log(LA_ERR, "  msg: $_") foreach (split(/\n/, $msg));
+    } else {
+        unlink($filename);
+    }
+    $res
+}
+
+sub _comment {
+    my ($self, $ozone, $message, @args) = @_;
+    my $com_prefix =
+        $ozone->get_attributes('type') =~ /^(dhcp|puppet|radius)$/ ? '#' : ';';
+
+    if ($message) {
+        return(sprintf("$com_prefix $message", @args));
+    } else {
+        $com_prefix
+    }
+}
+
+sub _comment_zone {
+    my ($self, $ozone) = @_;
+
+    my @output = ();
+    my $com_prefix =
+        $ozone->get_attributes('type') =~ /^(dhcp|puppet|radius)$/ ? '# ' : '; ';
+    push @output, sprintf('Zone %s, type %s', $ozone->id,
+        $ozone->get_attributes('type'));
+    push @output, $ozone->get_attributes('description')
+        if ($ozone->get_attributes('description'));
+    push @output, sprintf('Generated by %s', q$Id: BuildNet.pm 6283 2011-05-20 10:16:51Z nanardon $ );
+    push @output, sprintf('          the %s', scalar(localtime) );
+    push @output, sprintf('Network: %s', join(', ', $ozone->get_attributes('net')))
+        if ($ozone->get_attributes('net'));
+    push @output, sprintf('Exclude Network: %s', join(', ',
+            $ozone->get_attributes('netExclude')))
+        if ($ozone->get_attributes('netExclude'));
+    if ($ozone->get_attributes('type') =~ /^(dhcp|radius)$/) {
+        my @dynFrom = grep { $_ } $ozone->get_attributes('dynFrom');
+        push(@output, sprintf('This zone include host from zone: %s', join(', ', sort @dynFrom)))
+            if (@dynFrom);
+        push(@output, 'This zone include dynamic IP address')
+            if ($ozone->get_attributes('allow_dyn'));
+    }
+
+    return to_ascii(join('', map { $com_prefix . $_ . "\n" } @output) . "\n");
+}
+
+sub _comment_nethost {
+    my ($self, $nethost) = @_;
+
+    my $displayeduser;
+    if (my $owner = $nethost->get_attributes('user')) {
+        if (my $user = $self->_base->get_object('user', $owner)) {
+            $displayeduser = $user->get_attributes('displayName');
+        }
+    }
+    if ((!$displayeduser) && (my $owner = $nethost->get_attributes('owner'))) {
+        if (my $user = $self->_base->get_object('user', $owner)) {
+            $displayeduser = $user->get_attributes('displayName');
+        }
+    }
+    my @desc = (
+        $displayeduser,
+        $nethost->get_attributes('site'),
+        $nethost->get_attributes('physicalDeliveryOfficeName'),
+        $nethost->get_attributes('description'),
+    );
+
+    return to_ascii(join(', ', grep { $_ } @desc) || '');
+}
+
+
+# return undef on fatal error, depending zone type
+sub _read_template {
+    my ($self, $ozone) = @_;
+
+    my $revision = $self->get_zone_rev($ozone) or return;
+    if (open(my $handle, '<', $self->_template_file($ozone))) {
+        my $textzone = '';
+        while (my $line = <$handle>) {
+            $line =~ s/(\d+\s*;\s*)?\@REVISION@/$revision/;
+            $textzone .= $line;
+        }
+        close($handle);
+        return $textzone;
+    } else {
+        if ($ozone->get_attributes('type') =~ /^(dns|reverse)$/) {
+            la_log(LA_ERR, "Can't open template file for zone %s: %s", $ozone->id, $!);
+            return;
+        } else {
+            return '';
+        }
+    }
+}
+
+sub _gen_dns_zone {
+    my ($self, $ozone) = @_;
+
+    my $dbzone = '';
+    if ($ozone->get_attributes('net')) {
+        my $findhost = $self->_base->db->prepare_cached(q{
+            select name, value::inet as value from nethost join nethost_attributes_ips on
+            nethost.ikey = nethost_attributes_ips.okey
+            where value::inet <<= any(?) and exported = true
+            except
+            select name, value::inet from nethost join nethost_attributes_ips on
+            nethost.ikey = nethost_attributes_ips.okey
+            where value::inet <<= any(?)
+            order by value, name
+        });
+        $findhost->execute(
+            [ $ozone->get_attributes('net') ],
+            [ $ozone->get_attributes('netExclude') ],
+        ) or do {
+            la_log LA_ERR, "Cannot fetch host list: %s",
+                $self->_base->db->errstr;
+            return;
+        };
+        my %lists;
+        my %names;
+        # Storing all name in %names to check later if CNAME does not conflict
+        while (my $res = $findhost->fetchrow_hashref) {
+            $lists{$res->{name}} ||= {};
+            push(@{$lists{$res->{name}}{ip}}, $res->{value});
+            my $host_o = $self->_base->get_object('nethost', $res->{name});
+            foreach (grep { $_ } $host_o->get_attributes('otherName')) {
+                $names{$_} = 1;
+            }
+        }
+
+        foreach my $res (sort keys %lists) {
+            my $host_o = $self->_base->get_object('nethost', $res) or do {
+                la_log LA_ERR, "Cannot fetch host %s", $res->{name};
+                return;
+            };
+            my $desc = $self->_comment_nethost($host_o);
+            $dbzone .= $desc
+                ? '; ' . $desc . "\n"
+                : '';
+            foreach my $ip (@{$lists{$res}{ip}}) {
+                $dbzone .= sprintf(
+                    "%-30s IN    %-4s     %s\n",
+                    $res,
+                    ($ip =~ /:/ ? 'AAAA' : 'A'),
+                    $ip
+                );
+            }
+            foreach (grep { $_ } $host_o->get_attributes('otherName')) {
+                foreach my $ip (@{$lists{$res}{ip}}) {
+                    $dbzone .= sprintf(
+                        "%-30s IN    %-4s     %s\n",
+                        $_,
+                        ($ip =~ /:/ ? 'AAAA' : 'A'),
+                        $ip
+                    );
+                }
+            }
+            foreach (grep { $_ } $host_o->get_attributes('cname')) {
+                # It is deny to have both:
+                # foo IN A
+                # foo IN CNAME
+                if ($names{$_}) {
+                    my $msg .= sprintf(
+                        'Cname %s to %s exclude because %s is already an A record',
+                        $_, $res, $_
+                    );
+                    la_log(LA_ERR, sprintf("$msg (zone %s)", $ozone->id));
+                    $dbzone .= "; $msg\n";
+                } else {
+                    $dbzone .= sprintf("%-30s IN    CNAME    %s\n", $_, $res,);
+                }
+            }
+            # SSH finger print
+            foreach my $name (grep { $_ } ($res, $host_o->get_attributes('otherName'))) {
+                foreach my $sshfp (grep { $_ } $host_o->get_attributes('sshfp')) {
+                    $dbzone .= sprintf(
+                        "%-30s IN    SSHFP    %s\n",
+                        $name,
+                        $sshfp,
+                    );
+                }
+            }
+        }
+    }
+
+    return $dbzone;
+}
+
+sub _gen_reverse_zone {
+    my ($self, $ozone) = @_;
+
+    my $domain = $ozone->get_attributes('domain') || '';
+    my $dbzone = '';
+    if ($ozone->get_attributes('net')) {
+        my $findhost = $self->_base->db->prepare_cached(q{
+            select * from (
+            select * from nethost join nethost_attributes_ips on
+            nethost.ikey = nethost_attributes_ips.okey
+            where value::inet <<= ? and exported = true
+            except
+            select * from nethost join nethost_attributes_ips on
+            nethost.ikey = nethost_attributes_ips.okey
+            where value::inet <<= any(?)
+            ) as q
+            order by value::inet
+
+        });
+        $findhost->execute(
+            $ozone->get_attributes('net'),
+            [ $ozone->get_attributes('netExclude') ],
+        ) or do {
+            la_log LA_ERR, "Cannot fetch host list: %s",
+                $self->_base->db->errstr;
+            return;
+        };
+
+        # reverse is complicated:
+	my ($net) = $ozone->get_attributes('net') or do {
+        $self->base->log('Cannot fetch attribute "net" for zone %s', $ozone->id);
+        return;
+    };
+	my $netip = Net::IP->new($net) or do {
+		$self->base->log(LA_ERR, "Cannot build reverse zone %s: wrong net %s", $ozone->id, $net);
+		return;
+	};
+	my $mask = $netip->prefixlen;
+
+        while (my $res = $findhost->fetchrow_hashref) {
+            my $host_o = $self->_base->get_object('nethost', $res->{name}) or do {
+                la_log LA_ERR, "Cannot fetch host %s", $res->{name};
+                return;
+            };
+            my $desc = $self->_comment_nethost($host_o);
+            my $reverse = $host_o->get_attributes('reverse') || ($res->{name} . '.');
+            $dbzone .= $desc
+                ? '; ' . $desc . "\n"
+                : '';
+            my $revip;
+            my $fmt;
+            if ($res->{value} =~ /:/) {
+                # IPv6
+                my $m = $mask/4;
+                $revip = Net::IPv6Addr->new($res->{value})->to_string_ip6_int;
+                $revip =~ s/\.([0-9,a-f]\.?){$m}\.IP6\.INT\.$//i;
+                $fmt = "%-72s IN    PTR    %s%s\n";
+            } else {
+                # ipv4
+                if ($mask > 24) {
+                    $self->base->log(LA_ERR, 'Mask for zone %s cannot be %d', $ozone->id, $mask);
+                    return;
+                }
+                my @ippart = split(/\./, $res->{value});
+                splice(@ippart, 0, $mask/8); # get rid of start of ip
+                my @nippart;
+                while (@ippart) { unshift(@nippart, shift(@ippart)) }
+                $revip = join('.', @nippart);
+                $fmt = "%-12s IN    PTR    %s%s\n";
+            }
+ 
+            $dbzone .= sprintf($fmt, $revip,
+                $reverse =~ /\.$/
+                    ? ($reverse, ($domain ? "$domain." : '.'))
+                    : ($reverse, '.'));
+        }
+    }
+
+    return $dbzone;
+}
+
+sub _gen_dhcp_zone {
+    my ($self, $ozone) = @_;
+
+    my $outzone = $ozone;
+    my $output = '';
+
+    my @net;
+    if ($outzone->get_attributes('net')) {
+        @net = (map { Net::IP->new($_) } $outzone->get_attributes('net')) or do {
+            la_log(LA_DEBUG, 'Cannot get Net::IP for zone %s (ip: %s)', $outzone->id,
+                join(', ', $outzone->get_attributes('net')));
+            next;
+        };
+    }
+
+    {
+        my $find = $self->_base->db->prepare(q{
+            select * from nethost where exported = true and ikey in(
+            select okey from nethost_attributes where attr = 'macaddr'
+            intersect (
+                select nethost_attributes_ips.okey from nethost_attributes_ips join
+                netzone_attributes
+                on netzone_attributes.attr = 'net' and
+                netzone_attributes.value::inet >>= nethost_attributes_ips.value::inet
+                join netzone on netzone.ikey = netzone_attributes.okey
+                where netzone.name = $1
+
+                except
+                select nethost_attributes_ips.okey from nethost_attributes_ips join
+                netzone_attributes
+                on netzone_attributes.attr = 'netExclude' and
+                netzone_attributes.value::inet >>= nethost_attributes_ips.value::inet
+                join netzone on netzone.ikey = netzone_attributes.okey
+                where netzone.name = $1
+                )
+            )
+            order by name
+
+            });
+        $find->execute($ozone->id) or do {
+            la_log LA_ERR, "Cannot fetch host list: %s",
+                $self->_base->db->errstr;
+            return;
+        };
+        while (my $res = $find->fetchrow_hashref) {
+            my $nethost = $res->{name};
+
+            my $obj = $self->_base->get_object('nethost', $nethost) or do {
+                la_log LA_ERR, "Cannot fetch host %s", $res->{name};
+                return;
+            };
+
+            my @retainip;
+            if (@net) {
+                foreach my $inet (@net) {
+                    push(@retainip, grep { $_ && $inet->overlaps(Net::IP->new($_)) } $obj->get_attributes('ip'));
+                }
+            }
+
+            $obj->get_attributes('noDynamic') && !@retainip and next;
+
+            my $desc = $self->_comment_nethost($obj);
+            foreach my $mac (sort grep { $_ } $obj->get_attributes('macaddr')) {
+                my %done;
+                foreach my $retainip (sort @retainip) {
+                    $output .= $desc
+                       ? '# ' . $desc . "\n"
+                        : '';
+                    my ($fixedparam, $label, $netLockIp) = $retainip =~ /^\d+(\.\d+){3}$/
+                        ? ('fixed-address', '', '127.0.0.1')
+                        : ('fixed-address6', '-6', '::1');
+                    # If two ip are given for same protocol keep only first
+                    $done{$fixedparam} and next;
+                    $done{$fixedparam} = 1;
+                    my $fmac = $mac;
+                    $fmac =~ s/://g;
+                    $output .= sprintf("host %s-%s%s {\n", $nethost, lc($fmac), $label);
+                    $output .= sprintf("    hardware ethernet %s;\n", $mac);
+                    if ($obj->get_attributes('netLocked')) {
+                        $output .= sprintf("    $fixedparam %s;\n", $netLockIp);
+                    } else {
+                        $output .= sprintf("    $fixedparam %s;\n", $retainip)
+                            if ($retainip);
+                    }
+                    $output .= "}\n\n";
+                }
+            }
+        }
+    }
+    if ($ozone->get_attributes('allow_dyn')) {
+        $output .= "\n# Host without IP:\n";
+        my @dynfrom = grep { $_ } $ozone->get_attributes('dynFrom');
+        my $find = $self->_base->db->prepare(q{
+            select * from nethost where exported = true and ikey in(
+            select okey from nethost_attributes where attr = 'macaddr'
+            } . (@dynfrom ? q{
+            intersect
+            (
+                select ikey from nethost where ikey not in
+                    (select okey from nethost_attributes_ips)
+                union
+
+                (
+                select nethost_attributes_ips.okey from nethost_attributes_ips join
+                netzone_attributes
+                on netzone_attributes.attr = 'net' and
+                   netzone_attributes.value::inet >>=
+                   nethost_attributes_ips.value::inet
+                   join netzone on netzone.ikey = netzone_attributes.okey
+                   where netzone.name = any(?)
+                except
+                select nethost_attributes_ips.okey from nethost_attributes_ips join
+                netzone_attributes
+                on netzone_attributes.attr = 'netExclude' and
+                   netzone_attributes.value::inet >>=
+                   nethost_attributes_ips.value::inet
+                   join netzone on netzone.ikey = netzone_attributes.okey
+                   where netzone.name = any(?)
+                )
+            )} : '') . q{
+            except
+            select nethost_attributes_ips.okey from nethost_attributes_ips join
+            netzone_attributes
+            on netzone_attributes.attr = 'net' and
+            netzone_attributes.value::inet >>= nethost_attributes_ips.value::inet
+            join netzone on netzone.ikey = netzone_attributes.okey
+            where netzone.name = ?
+            )
+            order by name
+
+            });
+        $find->execute((@dynfrom ? ([ @dynfrom ], [ @dynfrom ]) : ()), $ozone->id) or do {
+            la_log LA_ERR, "Cannot fetch host list: %s",
+                $self->_base->db->errstr;
+            return;
+        };
+        while (my $res = $find->fetchrow_hashref) {
+            my $nethost = $res->{name};
+
+            my $obj = $self->_base->get_object('nethost', $nethost);
+
+            $obj->get_attributes('noDynamic') and next;
+
+            my $desc = $self->_comment_nethost($obj);
+            foreach my $mac (sort grep { $_ } $obj->get_attributes('macaddr')) {
+                $output .= $desc
+                    ? '# ' . $desc . "\n"
+                    : '';
+                my $fmac = $mac;
+                $fmac =~ s/://g;
+                if ($obj->get_attributes('netLocked')) {
+                    my ($ipv4, $ipv6);
+                    foreach ($ozone->get_attributes('net')) {
+                        if ($_ =~ /^\d+(\.\d+){3}\/(\d+)$/) {
+                            $ipv4 = 1;
+                        } else {
+                            $ipv6 = 1;
+                        }
+                    }
+
+                    if ($ipv4) {
+                        $output .= sprintf("host %s-%s {\n", $nethost, lc($fmac));
+                        $output .= sprintf("    hardware ethernet %s;\n", $mac);
+                        $output .= sprintf("    fixed-address 127.0.0.1;\n");
+                        $output .= "}\n\n";
+                    }
+                    if ($ipv6) {
+                        $output .= sprintf("host %s-%s {\n", $nethost, lc($fmac));
+                        $output .= sprintf("    hardware ethernet %s;\n", $mac);
+                        $output .= sprintf("    fixed-address6 ::1\n");
+                        $output .= "}\n\n";
+                    }
+                } else {
+                    $output .= sprintf("host %s-%s {\n", $nethost, lc($fmac));
+                    $output .= sprintf("    hardware ethernet %s;\n", $mac);
+                    $output .= "}\n\n";
+                }
+            }
+        }
+    }
+
+    $output
+}
+
+sub _gen_radius_zone {
+    my ($self, $ozone) = @_;
+
+    my $outzone = $ozone;
+    my $output = '';
+
+    my @net;
+    if ($outzone->get_attributes('net')) {
+        @net = (map { Net::IP->new($_) } $outzone->get_attributes('net')) or do {
+            la_log(LA_DEBUG, 'Cannot get Net::IP for zone %s (ip: %s)', $outzone->id,
+                join(', ', $outzone->get_attributes('net')));
+            next;
+        };
+    }
+
+    my %hosts;
+
+    {
+        my $find = $self->_base->db->prepare(q{
+            select * from nethost where exported = true and ikey in(
+            select okey from nethost_attributes where attr = 'macaddr'
+            intersect (
+                select nethost_attributes_ips.okey from nethost_attributes_ips join
+                netzone_attributes
+                on netzone_attributes.attr = 'net' and
+                netzone_attributes.value::inet >>= nethost_attributes_ips.value::inet
+                join netzone on netzone.ikey = netzone_attributes.okey
+                where netzone.name = $1
+
+                except
+                select nethost_attributes_ips.okey from nethost_attributes_ips join
+                netzone_attributes
+                on netzone_attributes.attr = 'netExclude' and
+                netzone_attributes.value::inet >>= nethost_attributes_ips.value::inet
+                join netzone on netzone.ikey = netzone_attributes.okey
+                where netzone.name = $1
+                )
+            )
+            order by name
+
+            });
+        $find->execute($ozone->id) or do {
+            la_log LA_ERR, "Cannot fetch host list: %s",
+                $self->_base->db->errstr;
+            return;
+        };
+        while (my $res = $find->fetchrow_hashref) {
+            $hosts{ $res->{name} } = 1;
+        }
+    }
+    if ($ozone->get_attributes('allow_dyn')) {
+        my @dynfrom = grep { $_ } $ozone->get_attributes('dynFrom');
+        my $find = $self->_base->db->prepare(q{
+            select * from nethost where exported = true and ikey in(
+            select okey from nethost_attributes where attr = 'macaddr'
+            } . (@dynfrom ? q{
+            intersect
+            (
+                select ikey from nethost where ikey not in
+                    (select okey from nethost_attributes_ips)
+                union
+
+                (
+                select nethost_attributes_ips.okey from nethost_attributes_ips join
+                netzone_attributes
+                on netzone_attributes.attr = 'net' and
+                   netzone_attributes.value::inet >>=
+                   nethost_attributes_ips.value::inet
+                   join netzone on netzone.ikey = netzone_attributes.okey
+                   where netzone.name = any(?)
+                except
+                select nethost_attributes_ips.okey from nethost_attributes_ips join
+                netzone_attributes
+                on netzone_attributes.attr = 'netExclude' and
+                   netzone_attributes.value::inet >>=
+                   nethost_attributes_ips.value::inet
+                   join netzone on netzone.ikey = netzone_attributes.okey
+                   where netzone.name = any(?)
+                )
+            )} : '') . q{
+            except
+            select nethost_attributes_ips.okey from nethost_attributes_ips join
+            netzone_attributes
+            on netzone_attributes.attr = 'net' and
+            netzone_attributes.value::inet >>= nethost_attributes_ips.value::inet
+            join netzone on netzone.ikey = netzone_attributes.okey
+            where netzone.name = ?
+            )
+            order by name
+
+            });
+        $find->execute((@dynfrom ? ([ @dynfrom ], [ @dynfrom ]) : ()), $ozone->id) or do {
+            la_log LA_ERR, "Cannot fetch host list: %s",
+                $self->_base->db->errstr;
+            return;
+        };
+        while (my $res = $find->fetchrow_hashref) {
+            $hosts{ $res->{name} } = 1;
+        }
+    }
+
+    my @parameters = grep { $_ } $outzone->get_attributes('hostParams');
+    foreach my $nethost (sort keys %hosts) {
+        my $obj = $self->_base->get_object('nethost', $nethost);
+
+        $obj->get_attributes('noDynamic') and next;
+
+        $output .= "# Host: $nethost\n";
+        my $desc = $self->_comment_nethost($obj);
+        $output .= $desc
+            ? '# ' . $desc . "\n"
+            : '';
+        foreach my $mac (sort grep { $_ } $obj->get_attributes('macaddr')) {
+            my $fmac = $mac;
+            $fmac =~ s/://g;
+            $output .= sprintf("%s Auth-Type := EAP, User-Password == \"%s\"\n", lc($fmac), lc($fmac));
+            $output .= join (",\n", map { "    $_" } @parameters) . "\n" if(@parameters);
+            $output .= "\n";
+        }
+        $output .= "\n";
+    }
+
+    $output;
+}
+
+sub _gen_puppet_zone {
+    my ($self, $ozone) = @_;
+
+    my $output = '';
+
+    if ($ozone->get_attributes('net')) {
+        my $findhost = $self->_base->db->prepare_cached(q{
+            select * from (
+            select * from nethost join nethost_attributes_ips on
+            nethost.ikey = nethost_attributes_ips.okey
+            where value::inet <<= ? and exported = true
+            except
+            select * from nethost join nethost_attributes_ips on
+            nethost.ikey = nethost_attributes_ips.okey
+            where value::inet <<= any(?)
+            ) as q
+            order by value::inet
+
+        });
+        $findhost->execute(
+            $ozone->get_attributes('net'),
+            [ $ozone->get_attributes('netExclude') ],
+        ) or do {
+            la_log LA_ERR, "Cannot fetch host list: %s",
+                $self->_base->db->errstr;
+            return;
+        };
+
+        my @puppetclasses = $ozone->get_attributes('puppetClass');
+        while (my $res = $findhost->fetchrow_hashref) {
+            my $nethost = $res->{name};
+
+            my $obj = $self->_base->get_object('nethost', $nethost) or do {
+                la_log LA_ERR, "Cannot fetch host %s", $res->{name};
+                return;
+            };
+
+            # merging classes from host and zone
+            my %classes = map { $_ => 1 } grep { $_ } (
+                $obj->get_attributes('puppetClass'),
+                ($obj->get_attributes('noInheritPuppet') ? () : @puppetclasses),
+            );
+            my $desc = $self->_comment_nethost($obj);
+            $output .= sprintf("node '%s' {\n", $obj->id);
+            $output .= $desc
+                ? sprintf("    # %s\n", $desc)
+                : '';
+            $output .= join('', map { "    class '$_'\n" } sort keys %classes);
+            $output .= "}\n\n";
+        }
+    }
+
+    $output
+}
+
+sub reset_savepoint {
+    my ($self) = @_;
+    foreach my $zone ($self->_base->search_objects('netzone')) {
+        my $ozone = $self->_base->get_object('netzone', $zone)
+            or next;
+
+        $self->_bnet_state->newval($ozone->id, 'dbrev', 0);
+        la_log LA_DEBUG, "Zone savepoint reset for %s", $ozone->id;
+        $self->_bnet_state->SetParameterComment(
+            $ozone->id, 'dbrev',
+            'Reset savepoint the ' . scalar(localtime));
+    }
+
+    $self->_write_state_file;
+}
+
+1;
+
+__END__
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Delexpiredusers.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Delexpiredusers.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Delexpiredusers.pm	(revision 2119)
@@ -0,0 +1,347 @@
+package LATMOS::Accounts::Task::Delexpiredusers;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+use DateTime;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Expiredusers - Warn and unexport expired user
+
+=head1 DESCRIPTION
+
+This module unexport user when they are expired since a given delay.
+
+Before deleting a warning can be sent to the manager.
+
+=head1 PARAMETER
+
+=head2 warndelay
+
+The number of days before warning manager the user will be unexported.
+
+If unset the warning is disable.
+
+=head2 unexportdelay
+
+The number of days after C<warndelay> the user is effectivelly unexported.
+
+If unset no user are unexported.
+
+=head2 modifdelay
+
+The time to wait in minutes after user modification before unexporting it.
+
+This time avoid the module deleting a user in the same time an administrator
+is trying to reactivate it.
+
+Default is 60 minutes.
+
+=head2 test
+
+If set only log are emitted but changes are not done and mail not really sent
+
+=head2 warn_manager
+
+If set, send a warning to manager of user after L<warndelay>.
+
+If not, a mail is sent only to L<summary> (if set).
+
+=head2 cc
+
+A copy of warning are sent to this email address
+
+=head2 summary
+
+If set, send a summmary to this address
+
+=head2 to
+
+Instead sending mail to manager mail are sent to this email address.
+
+=head1 warndelay/unexportdelay example
+
+Unexporting user immediatly /o any warning:
+
+    # warndelay = 0
+    unexportdelay = 0
+
+Warning manager with immediate unexport:
+
+    warndelay = 0
+    unexportdelay = 0
+
+Warning manager immediatelly but unexporting user 15 days later
+
+    warndelay = 0
+    unexportdelay = 15
+
+Wait 6 month after expiration, unexport user 15 days after warning:
+
+    warndelay = 180
+    unexportdelay = 15
+
+=cut
+
+# Run every 2 hours
+sub runDelay { 2 * 60 * 60 }
+
+# Wait 30 minutes
+sub waitDelay { 30 * 60 }
+
+sub _DATEFLAG { 'expireText' }
+
+sub init {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1);
+    my $labase = $self->{base} ? $LA->base($self->{base}) : $LA->base;
+    $labase && $labase->load or die "Cannot load base";
+
+    $self->{_la} = $LA;
+    $self->{_base} = $labase;
+
+    1;
+}
+
+sub run {
+    my ($self) = @_;
+
+    $self->_unexport;
+
+    $self->_send_warning;
+
+    1;
+}
+
+sub _send_warning {
+    my ($self) = @_;
+
+    my $test = $self->{syncm}->ini->val($self->{name}, 'test');
+    my $warnDelay = $self->{syncm}->ini->val($self->{name}, 'warndelay');
+
+    my $modifDelay = $self->{syncm}->ini->val($self->{name}, 'modifdelay', 60);
+    my $waitAfterModif = DateTime->now->subtract( minutes => $modifDelay  )->iso8601;
+       
+    defined($warnDelay)  or do {
+        $self->{_base}->log(LA_DEBUG, "Don't warning manager because no warndelay given");
+        return;
+    };
+
+    my $expiredDate = DateTime->now()->subtract(days => $warnDelay);
+
+    my %expired;
+
+    foreach my $user ($self->{_base}->search_objects('user',
+            'exported=1',
+            'expire<' . $expiredDate->ymd('-'),
+            'date<' . $waitAfterModif,
+            'oalias=NULL'
+        )) {
+        my $ouser = $self->{_base}->get_object('user', $user) or next;
+
+        if ($ouser->get_attributes('nextEmployment')) {
+            # This user has expire account but a futur employement is coming
+            next;
+        }
+
+
+        my $manager = $ouser->get_attributes('manager') || 'N/A';
+ 
+        $expired{$manager} ||= { users => [] };
+        push (@{$expired{$manager}{users}}, $ouser);
+    }
+
+    if ($self->{syncm}->ini->val('warn_manager')) {
+        my $lamail = LATMOS::Accounts::Mail->new(
+            $self->{_la},
+            'account_expired_reminder.mail',
+        );
+
+        my $MailSubject = $self->{_base}->la->val('_default_', 'mailSubject', 'LATMOS::Accounts');
+
+        foreach my $manager (sort keys %expired) {
+
+            my $omanager = $self->{_base}->get_object('user', $manager) or next;
+            my $mail = $omanager->get_attributes('mail') or next;
+
+            my $data = {
+                manager => $omanager,
+                users => [],
+            };
+
+            foreach my $ouser (@{ $expired{$manager}{users}}) {
+                if (my $val = $self->{_base}->get_global_value("expiredWarning.user." . $ouser->id)) {
+                    if ($val eq $ouser->get_attributes(_DATEFLAG)) {
+                        # Warning has been already sent
+                        next;
+                    }
+                }
+                $self->{_base}->log(
+                    LA_NOTICE,
+                    "%s Warning, user %s is expired (%s), manager %s",
+                    $test ? 'Test: ' : '',
+                    $ouser->id,
+                    $ouser->get_attributes('expireText'),
+                    $manager,
+                );
+                push(@{ $data->{users} }, $ouser);
+                $self->{_base}->set_global_value('expiredWarning.user.' . $ouser->id, $ouser->get_attributes(_DATEFLAG));
+            }
+            if (! @{ $data->{users} }) {
+                next;
+            }
+            $self->{_base}->commit;
+
+
+            my %mail = (
+                Subject => "$MailSubject: expired account",
+                'X-LATMOS-Reason' => 'Account destruction',
+            );
+
+            # to is for debuging
+            $mail{to} = $self->{syncm}->ini->val($self->{name}, 'to', $mail);
+            $mail{cc} = $self->{syncm}->ini->val($self->{name}, 'cc');
+
+            if ($test) {
+                $self->{_base}->log(LA_NOTICE, "Test: sending mail to %s for expired account %s",
+                    $omanager->id,
+                    join(', ', sort map { $_->id } @{ $data->{users} }),
+                );
+            } else {
+                if ($lamail->process(\%mail, $data)) {
+                    $self->{_base}->log(LA_NOTICE,
+                        "Expired account reminder mail sent to %s (%s) (cc: %s) for %s",
+                        $mail{to},
+                        $omanager->id,
+                        ($mail{cc} || ''),
+                        join(', ', sort map { $_->id } @{ $data->{users}}),
+                    );
+
+                }
+            }
+        }
+    }
+
+    if (my $sumto = $self->{syncm}->ini->val($self->{name}, 'summary')) {
+        my $MailSubject = $self->{_base}->la->val('_default_', 'mailSubject', 'LATMOS::Accounts');
+
+        my @summary;
+        foreach my $manager (sort keys %expired) {
+            foreach (@{$expired{$manager}{users}}) {
+
+                if (my $val = $self->{_base}->get_global_value("expiredWarning.usersummary." . $_->id)) {
+                    if ($val eq $_->get_attributes(_DATEFLAG)) {
+                        # Warning has been already sent
+                        next;
+                    }
+                }
+
+                push(@summary, sprintf("%s - %s (%s, %s)\n",
+                        $_->id,
+                        ($expired{$manager}{manager}
+                            ? $expired{$manager}{manager}->get_attributes('displayName')
+                            : $manager),
+                        $_->get_attributes('displayName'),
+                        $_->get_attributes('expireText'),
+                    )
+                );
+
+                $self->{_base}->set_global_value('expiredWarning.usersummary.' . $_->id, $_->get_attributes(_DATEFLAG));
+            }
+        }
+
+        $self->{_base}->commit;
+
+        if (@summary) {
+            if ($test) {
+                $self->{_base}->log(LA_NOTICE, "Test: no sending expired summary to $sumto for users");
+                $self->{_base}->log(LA_DEBUG, join('', @summary));
+            } else {
+                my %mail = (
+                    Subject => "$MailSubject: expired account (deactivation planned)",
+                    'X-LATMOS-Reason' => 'Account expiration',
+                    To => $sumto,
+                );
+                my $summail = LATMOS::Accounts::Mail->new(
+                    $self->{_la}, \join('', @summary), {}
+                );
+                if ($summail->process(\%mail)) {
+                    la_log(LA_NOTICE, "Expiration summary mail sent to %s",
+                        $sumto,
+                    );
+                }
+            }
+        }
+    }
+}
+
+sub _unexport {
+    my ($self) = @_;
+
+    my $test = $self->{syncm}->ini->val($self->{name}, 'test');
+
+    my $warnDelay     = $self->{syncm}->ini->val($self->{name}, 'warndelay', 0);
+    my $unexportDelay = $self->{syncm}->ini->val($self->{name}, 'unexportdelay');
+    defined($unexportDelay) or do {
+        $self->{_base}->log(LA_DEBUG, "Don't unexporting user because no unexportdelay given");
+        return;
+    };
+
+    my $expiredDate = DateTime->now()->subtract(days => $warnDelay + $unexportDelay);
+
+    my $modifDelay = $self->{syncm}->ini->val($self->{name}, 'modifdelay', 60);
+    my $waitAfterModif = DateTime->now->subtract( minutes => $modifDelay  )->iso8601;
+
+    foreach my $user ($self->{_base}->search_objects('user',
+            'exported=1',
+            'expire<' . $expiredDate->ymd('-'),
+            'date<' . $waitAfterModif,
+            'oalias=NULL'
+        )) {
+        my $ouser = $self->{_base}->get_object('user', $user) or next;
+        if ($ouser->get_attributes('currentEmployment') || $ouser->get_attributes('nextEmployment')) {
+            # This user has expire account but a futur employement is coming
+            next;
+        }
+
+        $self->{_base}->log(LA_NOTICE, "%sUser $user unexported because is expired (%s)",
+            $test ? 'Test: ' : '', $ouser->get_attributes('expireText'));
+
+        if (!$test) {
+            $ouser->_set_c_fields(
+                unexported => 1
+            );
+            $self->{_base}->del_global_value("expiredWarning.user.$user");
+            $self->{_base}->del_global_value("expiredWarning.usersummary.$user");
+            $ouser->ReportChange('Update', "Automatic unexport due to expiration");
+            $self->{_base}->commit;
+        }
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>, L<LATMOS::Accounts::Task>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Dummy.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Dummy.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Dummy.pm	(revision 2119)
@@ -0,0 +1,42 @@
+package LATMOS::Accounts::Task::Dummy;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts::Log;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Dummy - A "do nothing" task for testing purpose
+
+=head1 DESCRIPTION
+
+When setup in L<la-sync-manager> this task simulate an activity by waiting 2
+seconds and just exit successfully. 
+
+=cut
+
+sub run {
+    la_log LA_DEBUG, "Running dummy";
+    sleep 2;
+}
+
+1;
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts::Task>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Employment.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Employment.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Employment.pm	(revision 2119)
@@ -0,0 +1,110 @@
+package LATMOS::Accounts::Task::Employment;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+use LATMOS::Accounts::Bases::Sql::Employment;
+use DateTime;
+use DateTime::TimeZone;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Stats - Store value to perform some statistics
+
+=head1 DESCRIPTION
+
+=cut
+
+sub order { 0 }
+
+sub init {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1);
+    my $labase = $self->{base} ? $LA->base($self->{base}) : $LA->base;
+    $labase && $labase->load or die "Cannot load base";
+
+    $self->{_la} = $LA;
+    $self->{_base} = $labase;
+
+    1;
+}
+
+# Run it every 10 minutes, not more often
+sub runDelay { 10 * 60 }
+
+sub run {
+    my ($self) = @_;
+
+    my %users = map { $_ => undef } $self->{_base}->search_objects('user', 'exported=true');
+    my %old = $self->{_base}->attributes_summary_by_object('user', 'appliedEmployement');
+    foreach (keys %old) {
+        exists($users{$_}) or next;
+        $users{$_} = $old{$_}->[0] || '';
+    }
+
+    my $now = DateTime->now()->iso8601 . 'Z';
+
+    my $listempl = $self->{_base}->db->prepare_cached(q{
+        SELECT * FROM employment where firstday <= ?::timestamp and (lastday is null or lastday >= ?::timestamp - '1 days'::interval)
+            and exported = true
+            order by lastday, firstday
+    });
+    $listempl->execute($now, $now);
+    my %new  = ();
+    while (my $res = $listempl->fetchrow_hashref) {
+        my $employment = $res->{name};
+        my $user = $res->{user};
+        exists($users{$user}) or next;
+        # avoid undef:
+        $users{$user} ||= '';
+        if ($employment ne $users{$user}) {
+            my $ouser = $self->{_base}->get_object('user', $user) or next;
+
+            $self->{_base}->log(LA_NOTICE, "Updating user $user to match employment `%s'", $employment || '');
+            $ouser->applyCurrentEmployment;
+            $self->{_base}->commit;
+        }
+        delete($users{$user});
+    }
+
+    # Change has old employment => nothing
+    # if user is still active, expired account remains untouched
+    foreach my $user ($self->{_base}->search_objects('user', 'exported=true')) {
+        if ($users{$user}) {
+            my $ouser = $self->{_base}->get_object('user', $user);
+            if ($ouser->_resetEmployment) {
+                $self->{_base}->commit;
+            } else {
+                $self->{_base}->rollback;
+            }
+        }
+    }
+
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>, L<LATMOS::Accounts::Task>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Expiredaliases.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Expiredaliases.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Expiredaliases.pm	(revision 2119)
@@ -0,0 +1,103 @@
+package LATMOS::Accounts::Task::Expiredaliases;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Expiredaliases - Set aliases to expired account
+
+=head1 DESCRIPTION
+
+This module set forward to expired accounts to inform people the account is closed.
+
+The forward is set to c<destination> parameter found in C<la-sync-manager.ini>
+
+=cut
+
+# Every 10 minutes
+sub runDelay { 10 * 60 }
+
+sub init {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1);
+    my $labase = $self->{base} ? $LA->base($self->{base}) : $LA->base;
+    $labase && $labase->load or die "Cannot load base";
+
+    $self->{_la} = $LA;
+    $self->{_base} = $labase;
+
+    1;
+}
+
+sub run {
+    my ($self) = @_;
+
+    my $modifDelay = $self->{syncm}->ini->val($self->{name}, 'modifdelay', 60);
+    my $waitAfterModif = DateTime->now->subtract( minutes => $modifDelay  )->iso8601;
+
+    # Unset aliases when user get active:
+
+    my @aliases = $self->{_base}->search_objects('aliases',
+        'user.active=1',
+        'comment=EXPIREDACCOUNT',
+        'user.date<' . $waitAfterModif,
+    );
+    foreach (@aliases) {
+        $self->{_base}->log(LA_NOTICE, 'Deleting aliases %s because user is active', $_);
+        $self->{_base}->_delete_object('aliases', $_);
+        $self->{_base}->commit;
+    }
+
+    my $dest = $self->{syncm}->ini->val($self->{name}, 'destination') or do {
+        $self->{_base}->log(LA_ERR, 'No destination in config, cannot run Expiredaliases');
+        return 1;
+    };
+
+    my %existedAliases = map { $_ => 1 } $self->{_base}->search_objects('aliases', 'user.active=0');
+
+    foreach my $user ($self->{_base}->search_objects('user',
+            'status=expired',
+            'date<' . $waitAfterModif,
+            'oalias=NULL')
+        ) {
+        $existedAliases{$user} and next; # An alias existed, doing nothing
+        $self->{_base}->log(LA_NOTICE, "Adding alias to expired user $user");
+        $self->{_base}->create_object('aliases', $user,
+            comment     => 'EXPIREDACCOUNT',
+            description => "Forward because $user is expired, do not remove or change",
+            forward     => [ $dest, "\\$user" ],
+        );
+        $self->{_base}->commit;
+    }
+
+
+    1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>, L<LATMOS::Accounts::Task>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Iprecover.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Iprecover.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Iprecover.pm	(revision 2119)
@@ -0,0 +1,163 @@
+package LATMOS::Accounts::Task::Iprecover;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+use DateTime;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Iprecover - Recover IP from disabled hosts
+
+=head1 DESCRIPTION
+
+This module check for disabled hosts and remove the IP set on it to free them
+
+=head1 PARAMETER
+
+=head2 netzone
+
+The C<netzone> object containing concerned hosts
+
+=head2 filter
+
+A filter limiting host search.
+
+=head2 modifDelay
+
+The number of day hosts has not be modified in database.
+Hosts having recent changes are left unmodified.
+
+=head2 test
+
+If set only log are emitted but changes are not done and mail not really sent
+
+=cut
+
+sub runDelay { 2 * 60 * 60 }
+
+# Wait 30 minutes
+sub waitDelay { 30 * 60 }
+
+sub init {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1);
+    my $labase = $self->{base} ? $LA->base($self->{base}) : $LA->base;
+    $labase && $labase->load or die "Cannot load base";
+
+    $self->{_la} = $LA;
+    $self->{_base} = $labase;
+
+    1;
+}
+
+sub _filters {
+    my ($self) = @_;
+
+    my $filteredHosts;
+
+    if (my @cfilters = $self->{syncm}->ini->val($self->{name}, 'filter')) {
+        $filteredHosts = {};
+        foreach ($self->{_base}->search_objects('nethost', @cfilters)) {
+            $filteredHosts->{$_} = 1;
+        }
+    }
+
+    my $hosts;
+
+    if (my @zones = $self->{syncm}->ini->val($self->{name}, 'netzone')) {
+
+        $hosts = {};
+
+        my @filters;
+
+        foreach my $zone (@zones) {
+            la_log(LA_DEBUG, "Limiting affected host to zone %s", $zone);
+            my $oZone = $self->{_base}->get_object('netzone', $zone) or do {
+                la_log(LA_ERR, "Cannot get zone $zone named in config");
+                return;
+            };
+
+            my @zonehosts = $oZone->get_attributes('hosts');
+
+            push(@filters, [ 'name=' . join('||', @zonehosts) ]);
+
+            if ($oZone->get_attributes('allow_dyn')) {
+                la_log(LA_DEBUG, "Zone %s has allow_dyn set, searching w/o ip", $zone);
+                push(@filters, [ 'ip=NULL' ]);
+            }
+        }
+
+        foreach my $filter (@filters) {
+            foreach ($self->{_base}->search_objects('nethost', @$filter)) {
+                $filteredHosts && !$filteredHosts->{$_} and next;
+                $hosts->{$_} = 1;
+            }
+        }
+    } else {
+        $hosts = $filteredHosts;
+    }
+
+    return $hosts;
+}
+
+sub run {
+    my ($self) = @_;
+
+    $self->{_base}->temp_switch_unexported(sub {
+
+    my $test = $self->{syncm}->ini->val($self->{name}, 'test');
+
+    my $modifDelay = $self->{syncm}->ini->val($self->{name}, 'modifdelay', 366 * 2);
+    my $waitAfterModif = DateTime->now->subtract( days => $modifDelay  )->iso8601;
+
+    my $hosts = $self->_filters;
+
+    my @HostToDisable = $self->{_base}->search_objects('nethost', 'exported=0', 'ip=*', 'date<' . $waitAfterModif);
+
+    foreach my $hostname (@HostToDisable) {
+        if ($hosts) {
+            $hosts->{$hostname} or next;
+        }
+        my $Host = $self->{_base}->get_object('nethost', $hostname) or next;
+        $self->{_base}->log(
+            LA_NOTICE,
+            '%sRecovering IP for host %s because is disable from a long time',
+            ($test ? '(Test) ' : ''),
+            $hostname,
+        );
+        $Host->ReportChange('Update', "Recovering IP because host is disable from a long time");
+        $Host->_set_c_fields( ip => undef );
+    }
+
+    $self->{_base}->commit()
+        unless($test);
+
+    }, 1);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>, L<LATMOS::Accounts::Task>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Refreshexpired.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Refreshexpired.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Refreshexpired.pm	(revision 2119)
@@ -0,0 +1,95 @@
+package LATMOS::Accounts::Task::Refreshexpired;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Refreshexpired - Task to disable expired users
+
+=head1 DESCRIPTION
+
+Some base doesn't handle accounts expiration. One way to do it is to synchronize
+user with value denying login (wrong UNIX shell, password entry set to false,
+...).
+
+For performance issue, objects are not written at each synchronisation, only
+when they changes.
+
+This module find expired user and refresh modification time to ensure
+propagation to other database.
+
+=cut
+
+sub order { 0 }
+
+sub needupd { 1; }
+
+# Every 10 minutes
+sub runDelay { 10 * 60 }
+
+sub init {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1);
+    my $labase = $self->{base} ? $LA->base($self->{base}) : $LA->base;
+    $labase && $labase->load or die "Cannot load base";
+
+    $self->{_la} = $LA;
+    $self->{_base} = $labase;
+
+    1;
+}
+
+sub run {
+    my ($self) = @_;
+
+    my $find = $self->{_base}->db->prepare(
+        q{
+        SELECT * FROM "user"
+        WHERE expire < now() and "date" < expire;
+        }
+    );
+    $find->execute();
+    my $upd = $self->{_base}->db->prepare(
+        q{
+        UPDATE "user" SET date = now() where "name" = ?
+        }
+    );
+    while ( my $res = $find->fetchrow_hashref ) {
+        la_log(LA_NOTICE, "Refresh user %s to take expiry into account",
+            $res->{name},
+        );
+        $upd->execute($res->{name});
+    }
+
+    $self->{_base}->commit;
+
+    1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>, L<LATMOS::Accounts::Task>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Stats.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Stats.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Stats.pm	(revision 2119)
@@ -0,0 +1,79 @@
+package LATMOS::Accounts::Task::Stats;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Stats - Store value to perform some statistics
+
+=head1 DESCRIPTION
+
+=cut
+
+sub init {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1);
+    my $labase = $self->{base} ? $LA->base($self->{base}) : $LA->base;
+    $labase && $labase->load or die "Cannot load base";
+
+    $self->{_la} = $LA;
+    $self->{_base} = $labase;
+
+    1;
+}
+
+sub order { 2 }
+
+sub waitDelay { 5 * 60 }
+
+sub needupd { 1; }
+
+sub run {
+    my ($self) = @_;
+
+    $self->{_base}->temp_switch_unexported(
+        sub {
+            foreach my $stat ($self->{_base}->list_objects('stat')) {
+                my $ostat = $self->{_base}->get_object('stat', $stat);
+                # Don't process unexported stat objects
+                $ostat->_get_attributes('unexported') and next;
+                if ($ostat->collect) {
+                    $self->{_base}->commit;
+                } else {
+                    $self->{_base}->rollback;
+                }
+            }
+        },
+        1
+    );
+
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>, L<LATMOS::Accounts::Task>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Unexportexpired.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Unexportexpired.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Unexportexpired.pm	(revision 2119)
@@ -0,0 +1,86 @@
+package LATMOS::Accounts::Task::Unexportexpired;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+use DateTime;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Refreshexpired - Task to disable expired users
+
+=head1 DESCRIPTION
+
+Some base doesn't handle accounts expiration. One way to do it is to synchronize
+user with value denying login (wrong UNIX shell, password entry set to false,
+...).
+
+For performance issue, objects are not written at each synchronisation, only
+when they changes.
+
+This module find expired user and refresh modification time to ensure
+propagation to other database.
+
+=cut
+
+# Every 10 minutes
+sub runDelay { 20 * 60 }
+
+sub init {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1);
+    my $labase = $self->{base} ? $LA->base($self->{base}) : $LA->base;
+    $labase && $labase->load or die "Cannot load base";
+
+    $self->{_la} = $LA;
+    $self->{_base} = $labase;
+
+    1;
+}
+
+sub run {
+    my ($self) = @_;
+
+    my $now = DateTime->now();
+    my $nowtext = $now->iso8601();
+
+    foreach my $otype (qw(Aliases Nethost Address)) {
+
+        foreach my $name (
+            $self->{_base}->search_objects($otype, 'exported=t', "expire<$nowtext")) {
+            my $obj = $self->{_base}->get_object($otype, $name);
+            la_log(LA_NOTICE, "Unexport %s/%s object due to expire (%s)", $otype, $name, $obj->_get_attributes('expire') ||'');
+            $obj->_set_c_fields(unexported => 1);
+            $self->{_base}->commit;
+        }
+
+    }
+
+    1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>, L<LATMOS::Accounts::Task>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Unusedhosts.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Unusedhosts.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Unusedhosts.pm	(revision 2119)
@@ -0,0 +1,286 @@
+package LATMOS::Accounts::Task::Unusedhosts;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+use DateTime;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Unusedhosts - Disable host owned by inactive users
+
+=head1 DESCRIPTION
+
+This module disable nethost when owned by an expired or disabled user account, removing it from network
+
+=head1 PARAMETER
+
+=head2 netzone
+
+The C<netzone> object containing concerned hosts
+
+=head2 filter
+
+A filter limiting host search.
+
+=head2 disableMode
+
+The rules to disable hosts:
+
+=over 4
+
+=item C<owner>: C<owner> is set and is inactive
+
+=item C<user>: C<user> is set and is inactive
+
+=item C<both>: C<owner> or C<user> (if set) is inactive
+
+=item C<effective> (default): If C<user> is set and is inactive otherwise if
+C<owner> is inactive
+
+=back
+
+=head2 test
+
+If set only log are emitted but changes are not done and mail not really sent
+
+=cut
+
+# Wait 30 minutes
+sub waitDelay { 30 * 60 }
+
+sub init {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1);
+    my $labase = $self->{base} ? $LA->base($self->{base}) : $LA->base;
+    $labase && $labase->load or die "Cannot load base";
+
+    $self->{_la} = $LA;
+    $self->{_base} = $labase;
+
+    1;
+}
+
+sub run {
+    my ($self) = @_;
+
+    $self->_enable;
+
+    $self->_disable;
+
+    1;
+}
+
+sub _filters {
+    my ($self) = @_;
+
+    my $filteredHosts;
+
+    if (my @cfilters = $self->{syncm}->ini->val($self->{name}, 'filter')) {
+        $filteredHosts = {};
+        foreach ($self->{_base}->search_objects('nethost', @cfilters)) {
+            $filteredHosts->{$_} = 1;
+        }
+    }
+
+    my $hosts;
+
+    if (my @zones = $self->{syncm}->ini->val($self->{name}, 'netzone')) {
+
+        $hosts = {};
+
+        my @filters;
+
+        foreach my $zone (@zones) {
+            la_log(LA_DEBUG, "Limiting affected host to zone %s", $zone);
+            my $oZone = $self->{_base}->get_object('netzone', $zone) or do {
+                la_log(LA_ERR, "Cannot get zone $zone named in config");
+                return;
+            };
+
+            my @zonehosts = $oZone->get_attributes('hosts');
+
+            push(@filters, [ 'name=' . join('||', @zonehosts) ]);
+
+            if ($oZone->get_attributes('allow_dyn')) {
+                la_log(LA_DEBUG, "Zone %s has allow_dyn set, searching w/o ip", $zone);
+                push(@filters, [ 'ip=NULL' ]);
+            }
+        }
+
+        foreach my $filter (@filters) {
+            foreach ($self->{_base}->search_objects('nethost', @$filter)) {
+                $filteredHosts && !$filteredHosts->{$_} and next;
+                $hosts->{$_} = 1;
+            }
+        }
+    } else {
+        $hosts = $filteredHosts;
+    }
+
+    return $hosts;
+}
+
+sub _disable {
+    my ($self) = @_;
+
+    my $test = $self->{syncm}->ini->val($self->{name}, 'test');
+    my $mode = $self->{syncm}->ini->val($self->{name}, 'disableMode', 'effective');
+
+    la_log(LA_DEBUG, "Checking for host to disable using %s method", $mode);
+
+    my $hosts = $self->_filters;
+
+    my @HostToDisable;
+    for ($mode) {
+        /owner/i and do {
+            my @HostNoOwner = $self->{_base}->search_objects('nethost', 'exported=1', 'owner.active=0');
+            foreach (@HostNoOwner) {
+                push(@HostToDisable, $_);
+            }
+            last;
+        };
+        /user/i and do {
+            my @HostNoUser = $self->{_base}->search_objects('nethost', 'exported=1', 'user.active=0');
+            foreach (@HostNoUser) {
+                push(@HostToDisable, $_);
+            }
+            last;
+        };
+        /both/i and do {
+            my %HostNoUser  = map { $_ => } (
+                $self->{_base}->search_objects('nethost', 'exported=1', 'user.active=0'),
+                $self->{_base}->search_objects('nethost', 'exported=1', 'owner.active=0'),
+            );
+            foreach (keys %HostNoUser) {
+                push(@HostToDisable, $_);
+            }
+            last;
+        };
+        /effective/i and do {
+            my %HostNoUser  = map { $_ => } (
+                $self->{_base}->search_objects('nethost', 'exported=1', 'user.active=0'),
+                $self->{_base}->search_objects('nethost', 'exported=1', 'user=NULL', 'owner.active=0'),
+            );
+            foreach (keys %HostNoUser) {
+                push(@HostToDisable, $_);
+            }
+            last;
+        };
+
+        la_log(LA_ERR, "Unknown host check method `%s'", $mode);
+    }
+
+    foreach my $hostname (sort @HostToDisable) {
+        if ($hosts) {
+            $hosts->{$hostname} or next;
+        }
+        my $Host = $self->{_base}->get_object('nethost', $hostname) or next;
+        $self->{_base}->log(
+            LA_NOTICE,
+            '%sHost %s disable because owner or user is inactive',
+            ($test ? '(Test) ' : ''),
+            $hostname,
+        );
+        $Host->ReportChange('Update', "Disabling because user/owner is inactive");
+        $Host->_set_c_fields( unexported => 1 );
+    }
+
+    $self->{_base}->commit()
+        unless($test);
+}
+
+sub _enable {
+    my ($self) = @_;
+
+    my $test = $self->{syncm}->ini->val($self->{name}, 'test');
+    my $mode = $self->{syncm}->ini->val($self->{name}, 'disableMode', 'effective');
+
+    la_log(LA_DEBUG, "Checking for host to disable using %s method", $mode);
+
+    my $hosts = $self->_filters;
+
+    my @HostToEnable;
+    for ($mode) {
+        /owner/i and do {
+            my @HostNoOwner = $self->{_base}->search_objects('nethost', 'exported=0', 'owner.active=1');
+            foreach (@HostNoOwner) {
+                push(@HostToEnable, $_);
+            }
+            last;
+        };
+        /user/i and do {
+            my @HostNoUser = $self->{_base}->search_objects('nethost', 'exported=0', 'user.active=1');
+            foreach (@HostNoUser) {
+                push(@HostToEnable, $_);
+            }
+            last;
+        };
+        /both/i and do {
+            my %HostNoUser  = map { $_ => } (
+                $self->{_base}->search_objects('nethost', 'exported=0', 'user.active=1', 'owner.active=1'),
+                $self->{_base}->search_objects('nethost', 'exported=0', 'user=NULL', 'owner.active=1'),
+            );
+            foreach (keys %HostNoUser) {
+                push(@HostToEnable, $_);
+            }
+            last;
+        };
+        /effective/i and do {
+            my %HostNoUser  = map { $_ => } (
+                $self->{_base}->search_objects('nethost', 'exported=0', 'user.active=1'),
+                $self->{_base}->search_objects('nethost', 'exported=0', 'user=NULL', 'owner.active=1'),
+            );
+            foreach (keys %HostNoUser) {
+                push(@HostToEnable, $_);
+            }
+            last;
+        };
+
+        la_log(LA_ERR, "Unknown host check method `%s'", $mode);
+    }
+
+    foreach my $hostname (sort @HostToEnable) {
+        if ($hosts) {
+            $hosts->{$hostname} or next;
+        }
+        my $Host = $self->{_base}->get_object('nethost', $hostname) or next;
+        $self->{_base}->log(
+            LA_INFO,
+            '%sHost %s enable because owner or user is active',
+            ($test ? '(Test) ' : ''),
+            $hostname,
+        );
+        $Host->ReportChange('Update', "Enabling because user/owner is active");
+        $Host->_set_c_fields( unexported => 0 );
+    }
+
+    $self->{_base}->commit()
+        unless($test);
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>, L<LATMOS::Accounts::Task>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Updatedyndata.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Updatedyndata.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Updatedyndata.pm	(revision 2119)
@@ -0,0 +1,72 @@
+package LATMOS::Accounts::Task::Updatedyndata;
+
+use strict;
+use warnings;
+use base qw(LATMOS::Accounts::Task);
+use LATMOS::Accounts;
+use LATMOS::Accounts::Log;
+use LATMOS::Accounts::Utils;
+
+=head1 NAME
+
+LATMOS::Accounts::Task::Refreshexpired - Task to disable expired users
+
+=head1 DESCRIPTION
+
+Some base doesn't handle accounts expiration. One way to do it is to synchronize
+user with value denying login (wrong UNIX shell, password entry set to false,
+...).
+
+For performance issue, objects are not written at each synchronisation, only
+when they changes.
+
+This module find expired user and refresh modification time to ensure
+propagation to other database.
+
+=cut
+
+# Every 10 minutes
+sub runDelay { 10 * 60 }
+
+sub init {
+    my ($self) = @_;
+    my $LA = LATMOS::Accounts->new($self->{config});
+    my $labase = $self->{base} ? $LA->base($self->{base}) : $LA->base;
+    $labase && $labase->load or die "Cannot load base";
+
+    $self->{_la} = $LA;
+    $self->{_base} = $labase;
+
+    1;
+}
+
+sub run {
+    my ($self) = @_;
+
+    $self->{_base}->PopulateDynData;
+
+    1;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<LATMOS::Accounts>, L<LATMOS::Accounts::Task>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
Index: tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Utils.pm
===================================================================
--- tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Utils.pm	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/lib/LATMOS/Accounts/Utils.pm	(revision 2119)
@@ -0,0 +1,529 @@
+package LATMOS::Accounts::Utils;
+use 5.010000;
+use strict;
+use warnings;
+use Exporter ();
+use vars qw(@ISA @EXPORT_OK @EXPORT);
+use utf8;
+use LATMOS::Accounts::Log;
+use File::Temp qw(tempfile);
+use Crypt::Cracklib;
+use Text::CSV;
+
+our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
+
+=head1 NAME
+
+LATMOS::Accounts::Utils - Utils functions
+
+=head1 FUNCTIONS
+
+=cut
+
+@ISA = qw(Exporter);
+@EXPORT = qw(to_ascii exec_command switch_user run_via_sudo buildLogin loadCSV yesno);
+@EXPORT_OK = qw(to_ascii exec_command switch_user run_via_sudo buildLogin loadCSV yesno);
+
+=head2 to_ascii($text)
+
+Replace in C<$text> non ascii caracters from iso-8859-15 table to ascii
+equivalent caracter.
+
+=cut
+
+sub to_ascii {
+    my ($text) = @_;
+    return $text unless(defined($text));
+    utf8::decode($text) unless(utf8::is_utf8($text));
+    $text =~ s/Å/oe/g;
+    $text =~ s/ÃŠ/ae/g;
+    $text =~ tr {uÃ Ã¢Ã€ÃÃÃÃ§Ã©ÃšÃªÃ«ÃÃÃÃÃ¯Ã®ÃÃÃžÃ¶ÃŽÃÃÃŒÃ»ÃÃÄ}
+                {uaaaAAAceeeeEEEEiiIIoooOOuuUUc};
+    $text =~ s/([^[:ascii:]])/_/g;
+    $text
+} 
+
+=head2 exec_command($command, $env)
+
+Execute C<$command> and redirect output to log system.
+
+C<$env> is a hashref containing environment variable to set, all variables are
+prefixed by 'LA_'.
+
+=cut
+
+sub exec_command {
+    my ($command, $env) = @_;
+    my $rout = undef;
+    $rout = \$_[2] if(@_ > 2);
+
+    my @exec = ref $command
+        ? (@$command)
+        : ($command);
+    la_log(LA_DEBUG, 'running command `%s\'', join(' ', @exec));
+
+    pipe(my $rh, my $wh);
+    my $pid = fork;
+    if (!defined($pid)) {
+        la_log(LA_ERR, "Can't launch script: cannot fork");
+    } elsif ($pid) {
+        # Father
+        close($wh);
+        my $header;
+        while (<$rh>) {
+            if ($rout) {
+                $$rout .= $_;
+            } else {
+                chomp;
+                if (!$header) {
+                    $header = 1;
+                    la_log(LA_NOTICE, "exec `%s'", join(' ', @exec));
+                }
+                la_log(LA_NOTICE, "output: %s", $_);
+            }
+        }
+        waitpid($pid, 0);
+        if (my $exitstatus = $?) {
+            la_log(LA_ERR, 'command %s exit with status %d',
+                join(' ', @exec), $exitstatus);
+            return;
+        } else {
+            return 1;
+        }
+    } else {
+        # Child
+        close($rh);
+        ( $ENV{LA_MODULE} ) = caller();
+        foreach (keys %{ $env || {} }) {
+            $ENV{"LA_$_"} = $env->{$_};
+        }
+        open(STDOUT, ">&=" . fileno($wh));
+        open(STDERR, ">&=" . fileno($wh));
+        exec(@exec);
+        exit($!);
+    }
+    1
+}
+
+=head2 parse_obj_file($handle)
+
+Read file content from C<$handle> and return hash containing parsed attributes
+
+=cut
+
+sub parse_obj_file {
+    my ($handle) = @_;
+
+    my %attributes;
+    while (my $line = <$handle>) {
+        $line =~ /^#/ and next;
+        # Empty line are skipped (or space only)
+        $line =~ /^\s*$/ and next;
+        chomp($line);
+        my ($attr, $value) = $line =~ /^\s*(\S+):\s*(.*)\s*$/ or
+            die "Malformed input file\n";
+        $value =~ s/\s*$//;
+        $value =~ s/\\n/\n/g;
+        if ($attributes{$attr}) {
+            if (ref $attributes{$attr}) {
+                push(@{ $attributes{$attr} }, $value);
+            } else {
+                my $temp = $attributes{$attr};
+                $attributes{$attr} = [ $temp, $value ];
+            }
+        } else {
+            $attributes{$attr} = $value eq '' ? undef : $value;
+            # Don't remember why this is here
+            #$attr eq 'exported' && !defined $attributes{$attr} and $attributes{$attr} = 1;
+        }
+    }
+    %attributes
+}
+
+=head2 dump_read_temp_file($writecb, $readcb)
+
+Create a temporary file, call C<$writecb()> function, run editor and if file get
+modified call C<$readcb>.
+
+=cut
+
+sub dump_read_temp_file {
+    my ($writecb, $readcb) = @_;
+
+    my ($fh, $filename) = tempfile(CLEANUP => 0);
+    $writecb->($fh) or return;
+    $fh = undef; # closing file
+    my $res;
+    
+    my @stat = stat($filename);
+    while (1) {
+        my $cmd = ($ENV{EDITOR} || 'vi') . " $filename";
+        warn "Running $cmd\n";
+        if (system($cmd) == -1 ) {
+            warn "Cannot run editor $!\n";
+            last;
+        }
+        if ((stat($filename))[9] == $stat[9]) {
+            warn "No change existing\n";
+            last;
+        }
+
+        open($fh, '<', $filename) or return;
+        $res = $readcb->($fh);
+        $fh = undef; # closing again
+        $res < 2 and last;
+    }
+    unlink($filename);
+    $res;
+}
+
+=head2 loadCSV($fh, $callback, $initcallback)
+
+Parse CVS files and return an array for each parsed line.
+
+C<%options> may contains
+
+=over 4
+
+=item C<initcb> An function call after parsing first line
+
+=item C<cb> A function call for each line
+
+=back
+
+=cut
+
+sub loadCSV {
+    my ($fh, %opt) = @_;
+
+    my $csv = Text::CSV->new({
+            blank_is_undef => 1,
+            binary => 1,
+    });
+
+    binmode($fh, ":encoding(UTF-8)");
+
+    # First line contains attribute
+    my $columns = $csv->getline( $fh );
+
+    $csv->column_names($columns);
+
+    if ($opt{initcb}) {
+        $opt{initcb}->($csv);
+    }
+
+    my $all = [];
+    my $linecount = 1;
+    while ( my $row = $csv->getline_hr( $fh ) ) {
+        $linecount++;
+        if ($opt{cb}) {
+            if (! $opt{cb}->($row, $linecount)) {
+                return;
+            }
+        }
+        push(@{ $all }, $row);
+    }
+    $csv->eof () or do {
+        return;
+    };
+
+    return $all;
+}
+
+=head2 check_oid_validity($name)
+
+Check C<$name> is suitable to be used as object identifier. Return the error
+text, undef if no error.
+
+=cut
+
+sub check_oid_validity {
+    my ($name) = @_;
+    return "leadind space" if ($name =~ /^\s/);
+    return "trailing space" if ($name =~ /\s$/);
+    return "containing space" if ($name =~ /\s/);
+
+    return;
+}
+
+=head2 check_ug_validity($name)
+
+Check C<$name> is suitable to used as user or group identifier.
+
+=cut
+
+sub check_ug_validity {
+    my ($name) = @_;
+    return "Empty name is not a valid name !"
+        if (!$name);
+    return "first caractere must be a-z"
+        if ($name !~ /^[a-z]/);
+    return "must contain only a-z,0-9"
+        if ($name !~ /^[a-z,0-9,_,\-,\.]+$/);
+    return "lenght must be < 19 characters"
+        if (length($name) >= 20);
+
+    return check_oid_validity($name);
+}
+
+=head2 switch_user($runas)
+
+Switch effective id of the process to user named C<$runas>
+
+=cut
+
+sub switch_user {
+    my ($runas) = @_;
+
+    if ($< == 0 || $> == 0) {
+        my @info = getpwnam($runas) or do {
+            warn "Can find user $runas";
+            return;
+        };
+        $> = $info[3];
+        return;
+    } else {
+        warn "we are not root";
+    }
+}
+
+=head2 run_via_sudo($runas)
+
+Rerun current programme as C<$runas> user using sudo
+
+=cut
+
+sub run_via_sudo {
+    my ($runas) = @_;
+
+    my @info = getpwnam($runas) or do {
+        warn "Can find user $runas";
+        return;
+    }; 
+    if ($< != $info[3]) {
+        exec('sudo', '-u', $runas, $0, @ARGV) or "Can run $!";
+    }
+}
+
+=head2 genpassword(%options)
+
+Generate a random password, options are:
+
+=over 4
+
+=item length
+
+The minimum password length (default is 6)
+
+=item nonalpha
+
+Include non alpha-numeric caracters
+
+=item syllables
+
+Use a set of syllables instead letter
+
+=item checkpassword
+
+A sub reference to check if generated password is valid
+
+=back
+
+=cut
+
+sub genpassword {
+    my (%options) = @_;
+
+    $options{checkpassword} ||= sub {
+        my ($cpass) = @_;
+        return fascist_check($cpass) eq 'ok';
+    };
+
+    my @non_alpha = (qw$; : / ! ( ) [ ] { } + = @ - " ' % * & . ? < >$, ',', '$');
+    my @letters = ('a' .. 'z', 'A' .. 'Z', 0 .. 9);
+
+    my @consonants = qw(b d f g j k l m n r s t v x z ch);
+    my @vowels = qw(a e i o u ou oi io ia iu);
+
+    $options{length} ||= 8 + int(rand(3));
+
+    while (1) {
+        if ($options{syllables}) {
+            $options{length} = int($options{length} / 2);
+            $options{length} = 3 if ($options{length} < 3);
+        } else {
+            $options{length} = 6 if ($options{length} < 6);
+        }
+
+        my @chars;
+
+        if ($options{nonalpha}) {
+            for(0 .. (0 + int(rand(3)))) {
+                push(@chars, (@non_alpha[rand(scalar(@non_alpha))]));
+            }
+        }
+
+        foreach (1 .. ($options{length} - scalar(@chars))) {
+            if ($options{syllables}) {
+                my $c = @consonants[rand(scalar(@consonants))];
+                my $v = @vowels[rand(scalar(@vowels))];
+                push(@chars, "$c$v");
+            } else {
+                push(@chars, (@letters[rand(scalar(@letters))]));
+            }
+        }
+
+
+        my $pass = join('', sort { rand() <=> rand() } @chars);
+        if (length($pass) >= 6 && $options{checkpassword}->($pass)) {
+            return $pass;
+        }
+    }
+}
+
+=head2 Crypt($password, $method)
+
+Build an encrypted password using standard crypt(), $method is the encrypted method to use:
+
+=over 4
+
+=item DES: the old DES method, do not use
+
+=item 1 or md5
+
+=item 5 or sha-256
+
+=item 6 or sha-512
+
+=back
+
+=cut
+
+sub Crypt {
+    my ($clearpassword, $method) = @_;
+
+    $method ||= '';
+    my $methNumber = {
+        'des'     => -1,
+        'md5'     =>  1,
+        'sha-256' =>  5,
+        'sha-512' =>  6,
+    }->{lc($method)} || $method || 1;
+
+
+    if ($methNumber > 0) {
+        # Good we're secure
+        my @salt_char = (('a' .. 'z'), ('A' .. 'Z'), (0 .. 9), '/', '.');
+        my $salt = join('', map { $salt_char[rand(scalar(@salt_char))] } (1 .. 8));
+
+        return crypt($clearpassword, '$' . $methNumber . '$' . $salt);
+    } else {
+        # Grumpf DES
+        my @salt_char = (('a' .. 'z'), ('A' .. 'Z'), (0 .. 9));
+        my $salt = join('', map { $salt_char[rand(scalar(@salt_char))] } (1,2));
+
+        return crypt($clearpassword, $salt);
+    }
+}
+
+=head2 buildLogin([$cb, ] @names)
+
+Try to find a proper login from @names.
+
+Optionnal $cb is a callback to check the solution over exiting data. If return false, another solution is tried
+
+Example:
+
+    buildLogin(sub { $_[0] ? 1 : 0 }, "sn", "givenName")
+
+=cut
+
+sub buildLogin {
+    my (@names) = @_;
+    my $cb = undef;
+
+    if (ref $names[0]) {
+        $cb = shift(@names);
+    } else {
+        $cb = sub { 1 }; # always validating
+    }
+
+    # Cleaning names:
+    foreach (@names) {
+        $_ ||= '';
+        $_ = lc(to_ascii($_));
+        s/[^\w]//g;
+    }
+
+    @names = grep { $_ } @names;
+    my $base = shift(@names) or return;
+    my $rest = join('', grep { $_ } @names);
+
+    if ((my $l = length($base)) > 10) {
+        my $len = 8;
+        while ($len < $l) {
+            my $try  = substr($base, 0, $len);
+            if ($cb->($try)) {
+                return $try;
+            }
+            $len++;
+        }
+    } elsif ($cb->($base)) {
+        return $base;
+    }
+
+    my $try = $base;
+    while (my $len = length($try) - length($base) + 1) {
+        if ($len > length($rest)) {
+            last;
+        }
+        $try = $base . substr($rest, 0, $len);
+        if ($cb->($try)) {
+            return $try;
+        }
+    }
+    return;
+}
+
+=head2 yesno($value, $default)
+
+Check is a paramter mean yes or no
+
+=cut
+
+sub yesno {
+    my ($value, $default) = @_;
+
+    if ($value =~ /^(yes|1|true)$/i) {
+        return 1;
+    }
+    if ($value =~ /^(no|0|false)$/i) {
+        return 0;
+    }
+    if ($value =~ /^\d+$/ and $value != 0) {
+        return 1;
+    }
+    return $default || $value;
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+L<sudo>
+
+=head1 AUTHOR
+
+Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
Index: tags/5.2.23/LATMOS-Accounts/live-test/10_sql.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/live-test/10_sql.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/live-test/10_sql.t	(revision 2119)
@@ -0,0 +1,353 @@
+# -*- indent-tabs-mode: nil; tab-width: 4; -*-
+# vim:ft=perl:et:sw=4
+# $Id$
+
+use strict;
+use warnings;
+use Test::More;
+use FindBin qw($Bin);
+
+use DateTime;
+
+require "$Bin/common.pl";
+eval "require '$Bin/config.pl'";
+
+warn join(' ', @INC);
+
+if ($@) {
+    plan skip_all => 'no config found' if ($@);
+} elsif (!$LA::T::SQLBASE) {
+    plan skip_all => 'No SQL base defined';
+} else {
+    plan tests => 102; # Number of test
+}
+
+use_ok('LATMOS::Accounts');
+use_ok('LATMOS::Accounts::Bases::Sql');
+
+ok(my $la = LATMOS::Accounts->new(undef, noacl => 1), "Can get LATMOS::Accounts");
+ok(my $base = $la->base($LA::T::SQLBASE), "can get SQL base $LA::T::SQLBASE");
+
+{ # test: user
+my $oid = genid();
+
+use_ok('LATMOS::Accounts::Bases::Sql::User');
+ok($base->create_c_object('user', $oid,
+    sn => "snUser",
+    givenName => "givenNameUser",
+    expire => undef,
+    ), "Can create user $oid");
+ok(my $obj = $base->get_object('user', $oid), "user $oid is actually deleted");
+is($obj->get_attributes('sn'), "snUser");
+
+my $passwd = genid();
+ok($obj->set_password($passwd), "Can set password");
+ok($base->authenticate_user($oid, $passwd), "can authenticate user");
+ok(!$base->authenticate_user($oid, '----'),
+    "don't authenticate with invalid password");
+
+ok($base->delete_object('user', $oid), "Can delete user $oid");
+ok(!$base->get_object('user', $oid), "user $oid is actually deleted");
+}
+
+$base->rollback;
+
+{ # test alias
+
+my $oid = genid();
+
+ok($base->create_c_object('user', $oid,
+    sn => "snUser",
+    givenName => "givenNameUser",
+    expire => undef,
+    ), "Can create user $oid");
+ok(my $realobj = $base->get_object('user', $oid), "user $oid is actually created");
+
+my $aliasoid = genid();
+
+ok($base->CreateAlias('user', $aliasoid, $oid), "Can create alias object");
+
+ok(my $obj = $base->get_object('user', $aliasoid), "Can get object trought alias");
+is($obj->get_attributes('sn'), 'snUser');
+
+ok(my $aobj = $base->GetAlias('user', $aliasoid), "Can get alias object");
+
+is($aobj->get_attributes('oalias'), $oid);
+is($aobj->get_attributes('oaliascache'), $oid);
+
+my $oidsub = genid();
+ok($base->create_c_object('user', $oidsub,
+    sn => "snUserSub",
+    givenName => "givenNameSub",
+    expire => undef,
+    ), "Can create user $oid");
+ok(my $subobj = $base->get_object('user', $oidsub), "user $oidsub is actually created");
+
+$subobj->set_c_fields(
+    managerContact => $aliasoid,
+);
+
+is($subobj->get_c_field('manager'), $aliasoid, "Can get ref manager");
+is($subobj->GetAttributeValue('manager'), $oid, "Can get ref manager via GetAttributeValue");
+
+ok(eq_set(
+        [ $base->search_objects('user', 'sn=snUser') ],
+        [ $oid, $aliasoid ]
+    ), 'Search retrieve alias');
+
+ok(eq_set(
+        [ $base->search_objects('user', 'sn=snUser', 'oalias=NULL') ],
+        [ $oid ]
+    ), 'Search with oalias=NULL don\'t retrieve alias');
+
+ok(eq_set(
+        [ $base->search_objects('user', 'sn=XXXXXXXXX') ],
+        [ ]
+    ), 'Search with no result don\'t retrieve alias');
+
+ok($base->RemoveAlias('user', $aliasoid), "Removing Alias");
+ok(!$base->GetAlias('user', $aliasoid), "Cannot get removed alias object");
+
+$subobj->set_c_fields(
+    managerContact => $oid,
+);
+
+ok($base->CreateAlias('user', $aliasoid, "user.$oidsub.managerContact"), "Can create alias object");
+ok($aobj = $base->GetAlias('user', $aliasoid), "Can get alias object");
+is($aobj->get_attributes('oalias'), "user.$oidsub.managerContact");
+is($aobj->get_attributes('oaliascache'), $oid);
+
+ok($aobj->set_c_fields(oalias => $oidsub));
+is($aobj->get_attributes('oalias'), $oidsub);
+is($aobj->get_attributes('oaliascache'), $oidsub);
+
+ok($aobj->set_c_fields(oalias => "user.$oidsub.managerContact"));
+is($aobj->get_attributes('oalias'), "user.$oidsub.managerContact");
+is($aobj->get_attributes('oaliascache'), $oid);
+
+}
+
+$base->rollback;
+
+{ # test: group
+my $oid = genid();
+
+use_ok('LATMOS::Accounts::Bases::Sql::Group');
+ok($base->create_c_object('group', $oid,
+    description => 'Group Test'
+), "Can create group $oid");
+ok(my $obj = $base->get_object('group', $oid), "group $oid is actually deleted");
+is($obj->get_attributes('description'), "Group Test");
+
+ok($base->delete_object('group', $oid), "Can delete group $oid");
+ok(!$base->get_object('group', $oid), "group $oid is actually deleted");
+}
+
+$base->rollback;
+
+{ # test: nethost
+my $oid = genid();
+
+use_ok('LATMOS::Accounts::Bases::Sql::Nethost');
+ok($base->create_c_object('nethost', $oid,
+    description => 'Nethost Test',
+    ip => '255.255.255.255', # widely improbable to conflict with existing one
+), "Can create nethost $oid");
+ok(my $obj = $base->get_object('nethost', $oid), "nethost $oid is actually deleted");
+is($obj->get_attributes('description'), "Nethost Test");
+my ($ip) = $obj->get_attributes('ip');
+is($ip, '255.255.255.255');
+
+ok($base->delete_object('nethost', $oid), "Can delete nethost $oid");
+ok(!$base->get_object('nethost', $oid), "nethost $oid is actually deleted");
+}
+
+$base->rollback;
+
+{ # test: netzone
+my $oid = genid();
+
+use_ok('LATMOS::Accounts::Bases::Sql::Netzone');
+ok($base->create_c_object('netzone', $oid,
+    description => 'Netzone Test',
+    type => 'dhcp',
+), "Can create netzone $oid");
+ok(my $obj = $base->get_object('netzone', $oid), "netzone $oid is actually deleted");
+is($obj->get_attributes('description'), "Netzone Test");
+
+ok($base->delete_object('netzone', $oid), "Can delete netzone $oid");
+ok(!$base->get_object('netzone', $oid), "netzone $oid is actually deleted");
+}
+
+$base->rollback;
+
+{ # test employment
+use_ok('LATMOS::Accounts::Bases::Sql::Employment');
+my $baseEmp = genid();
+my $empUser = genid();
+
+my $baseEmpStart = DateTime->now->subtract(days => 60);
+my $baseEmpEnd   = DateTime->now->add(days => 60);
+
+ok($base->create_c_object('user', $empUser,
+    sn => "snUser",
+    givenName => "givenNameUser",
+    ), "Can create user $empUser");
+
+ok($base->create_c_object('employment', $baseEmp,
+    user => $empUser,
+    firstday => $baseEmpStart->dmy('/'),
+    lastday  => $baseEmpEnd->dmy('/'),
+), "Can create current employment");
+
+{
+    my $Emp      = genid(); 
+    my $EmpStart = DateTime->now->add(days => 61);
+    my $EmpEnd   = DateTime->now->add(days => 120);
+    ok($base->create_c_object('employment', $Emp,
+        user => $empUser,
+        firstday => $EmpStart->dmy('/'),
+        lastday  => $EmpEnd->dmy('/'),
+    ), "Can create employment after");
+    ok($base->delete_object('employment', $Emp), "Deleting Emp $Emp");
+}
+{
+    my $Emp      = genid(); 
+    my $EmpStart = DateTime->now->subtract(days => 40);
+    my $EmpEnd   = DateTime->now->subtract(days => 20);
+    ok(!$base->create_c_object('employment', $Emp,
+        user => $empUser,
+        firstday => $EmpStart->dmy('/'),
+        lastday  => $EmpEnd->dmy('/'),
+    ), "Cannot create employment overlaping start");
+    ok(!$base->delete_object('employment', $Emp), "Deleting Emp $Emp");
+}
+{
+    my $Emp      = genid(); 
+    my $EmpStart = DateTime->now->add(days => 30);
+    my $EmpEnd   = DateTime->now->add(days => 90);
+    ok(!$base->create_c_object('employment', $Emp,
+        user => $empUser,
+        firstday => $EmpStart->dmy('/'),
+        lastday  => $EmpEnd->dmy('/'),
+    ), "Cannot create employment overlapping end");
+    ok(!$base->delete_object('employment', $Emp), "Deleting Emp $Emp");
+}
+{
+    my $Emp      = genid(); 
+    my $EmpStart = DateTime->now->subtract(days => 90);
+    my $EmpEnd   = DateTime->now->add(days => 90);
+    ok(!$base->create_c_object('employment', $Emp,
+        user => $empUser,
+        firstday => $EmpStart->dmy('/'),
+        lastday  => $EmpEnd->dmy('/'),
+    ), "Cannot create employment overlapping another");
+    ok(!$base->delete_object('employment', $Emp), "Deleting Emp $Emp");
+}
+ok($base->delete_object('employment', $baseEmp), "Deleting Emp BaseEmp $baseEmp");
+
+{
+
+    $base->{_options}{allow_pasted_employment} = 1;
+    my $Emp      = genid();
+    my $EmpStart = DateTime->now->subtract(days => 90);
+    ok($base->create_c_object('employment', $Emp,
+        user => $empUser,
+        firstday => $EmpStart->dmy('/'),
+        lastday  => undef,
+    ), "Can create unlimited employment");
+    ok(my $ouser = $base->get_object('user', $empUser), "Getting user object");
+    ok(!$ouser->get_attributes('expire'), 'User don t expire');
+
+    my $EmpOld   = genid();
+    ok($base->create_c_object('employment', $EmpOld,
+        user => $empUser,
+        firstday => '1/1/2012',
+        lastday  => '31/12/2012',
+    ), "Cannot create pasted employement");
+
+    ok($ouser = $base->get_object('user', $empUser), "Getting user object");
+    ok(!$ouser->get_attributes('expire'), 'User still don t expire');
+
+    ok($base->delete_object('employment', $Emp), "Deleting Emp $Emp");
+    ok($base->delete_object('employment', $EmpOld), "Deleting Emp $EmpOld");
+    $base->{_options}{allow_pasted_employment} = undef;
+}
+
+ok($base->delete_object('user', $empUser), "Deleting user $empUser");
+}
+
+$base->rollback;
+
+{ 
+    diag('Testing search');
+ 
+    ok($base->create_c_object('user', 'searchuaa',
+        sn => "Lastnamea",
+        givenName => "Firstnamea",
+        ), "Can create user searchuaa");
+    ok($base->create_c_object('user', 'searchuna',
+        sn => "Lastnamea",
+        ), "Can create user searchuna");
+    ok($base->create_c_object('user', 'searchuab',
+        sn => "Lastnamea",
+        givenName => "Firstnameb",
+        ), "Can create user searchuab");
+    ok($base->create_c_object('user', 'searchuba',
+        sn => "Lastnameb",
+        givenName => "Firstnamea",
+        ), "Can create user searchuba");
+    ok($base->create_c_object('user', 'searchubb',
+        sn => "Lastnameb",
+        givenName => "Firstnameb",
+        ), "Can create user searchubb");
+    ok($base->create_c_object('group', 'searchga',
+            member => [ 'searchuaa', 'searchuba' ],
+        ), "Can create group searchga");
+    ok($base->create_c_object('group', 'searchgb',
+            member => [ 'searchuab', 'searchuba' ],
+        ), "Can create group searchgb");
+
+    ok(eq_set(
+        [ $base->search_objects('user', 'givenName=Firstnamea') ],
+        [ 'searchuaa', 'searchuba' ]
+    ), 'Can search on single attribute');
+    ok(eq_set(
+        [ $base->search_objects('user', 'givenName=Firstnamea', 'givenName=Firstnameb') ],
+        [ 'searchuaa', 'searchuab', 'searchuba', 'searchubb' ]
+    ), 'Can search on single attribute with 2 values');
+    ok(eq_set(
+        [ $base->search_objects('user', 'givenName=Firstnamea||Firstnameb') ],
+        [ 'searchuaa', 'searchuba', 'searchuab', 'searchubb' ]
+    ), 'Can search on single attribute with 2 values using ||');
+    ok(eq_set(
+        [ $base->search_objects('user', 'givenName=Firstnamea', 'sn=Lastnamea') ],
+        [ 'searchuaa' ]
+    ), 'Can search on 2 attributes');
+    ok(! $base->search_objects('user', 'givenName=Firstnamea', 'sn=NoName'), 'Can search on 2 attributes, one return nothing');
+    ok($base->search_objects('user'), 'No filter return something');
+    ok(eq_set(
+        [ $base->search_objects('user', 'givenName=NULL', 'sn=Lastnamea') ],
+        [ 'searchuna' ]
+    ), 'Can search on NULL attributes');
+    ok(eq_set(
+        [ $base->search_objects('user', 'memberOf=searchga&&searchgb') ],
+        [ 'searchuba' ],
+    ), "Search using && with two values");
+    ok(eq_set(
+        [ $base->search_objects('group', 'member.givenName=Firstnameb') ],
+        [ 'searchgb' ],
+    ), "Search using recursive search");
+    ok(eq_set(
+        [ $base->search_objects('user', 'givenName~nameb') ],
+        [ 'searchuab', 'searchubb' ]
+    ), 'Can search using ~ operator');
+
+    ok($base->delete_object('user', 'searchuaa'), "deleting user searchuaa");
+    ok($base->delete_object('user', 'searchuna'), "deleting user searchuna");
+    ok($base->delete_object('user', 'searchuba'), "deleting user searchuba");
+    ok($base->delete_object('user', 'searchuab'), "deleting user searchuab");
+    ok($base->delete_object('user', 'searchubb'), "deleting user searchubb");
+}
+
+$base->rollback;
Index: tags/5.2.23/LATMOS-Accounts/live-test/20_ldap.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/live-test/20_ldap.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/live-test/20_ldap.t	(revision 2119)
@@ -0,0 +1,62 @@
+# -*- indent-tabs-mode: nil; tab-width: 4; -*-
+# vim:ft=perl:et:sw=4
+# $Id$
+
+use strict;
+use warnings;
+use Test::More;
+use FindBin qw($Bin);
+
+require "$Bin/common.pl";
+eval "require '$Bin/config.pl'";
+
+if ($@) {
+    plan skip_all => 'no config found' if ($@);
+} elsif (!$LA::T::LDAPBASE) {
+    plan skip_all => 'No LDAP base defined';
+} else {
+    plan tests => 19; # Number of test
+}
+
+use_ok('LATMOS::Accounts');
+use_ok('LATMOS::Accounts::Bases::Ldap');
+
+ok(my $la = LATMOS::Accounts->new(undef, noacl => 1), "Can get LATMOS::Accounts");
+ok(my $base = $la->base($LA::T::LDAPBASE), "can get SQL base $LA::T::LDAPBASE");
+
+{ # test: user
+my $oid = genid();
+
+use_ok('LATMOS::Accounts::Bases::Ldap::User');
+ok($base->create_c_object('user', $oid,
+    sn => "snUser",
+    givenName => "givenNameUser",
+    ), "Can create user $oid");
+ok(my $obj = $base->get_object('user', $oid), "user $oid is actually deleted");
+is($obj->get_attributes('sn'), "snUser");
+
+my $passwd = genid();
+ok($obj->set_password($passwd), "Can set password");
+ok($base->authenticate_user($oid, $passwd), "can authenticate user");
+ok(!$base->authenticate_user($oid, '----'),
+    "don't authenticate with invalid password");
+
+ok($base->delete_object('user', $oid), "Can delete user $oid");
+ok(!$base->get_object('user', $oid), "user $oid is actually deleted");
+}
+
+
+{ # test: group
+my $oid = genid();
+
+use_ok('LATMOS::Accounts::Bases::Ldap::Group');
+ok($base->create_c_object('group', $oid,
+    description => 'Group Test'
+), "Can create group $oid");
+ok(my $obj = $base->get_object('group', $oid), "group $oid is actually deleted");
+is($obj->get_attributes('description'), "Group Test");
+
+ok($base->delete_object('group', $oid), "Can delete group $oid");
+ok(!$base->get_object('group', $oid), "group $oid is actually deleted");
+}
+
Index: tags/5.2.23/LATMOS-Accounts/live-test/30_ad.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/live-test/30_ad.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/live-test/30_ad.t	(revision 2119)
@@ -0,0 +1,62 @@
+# -*- indent-tabs-mode: nil; tab-width: 4; -*-
+# vim:ft=perl:et:sw=4
+# $Id$
+
+use strict;
+use warnings;
+use Test::More;
+use FindBin qw($Bin);
+
+require "$Bin/common.pl";
+eval "require '$Bin/config.pl'";
+
+if ($@) {
+    plan skip_all => 'no config found' if ($@);
+} elsif (!$LA::T::ADBASE) {
+    plan skip_all => 'No AD base defined';
+} else {
+    plan tests => 19; # Number of test
+}
+
+use_ok('LATMOS::Accounts');
+use_ok('LATMOS::Accounts::Bases::Ad');
+
+ok(my $la = LATMOS::Accounts->new(undef, noacl => 1), "Can get LATMOS::Accounts");
+ok(my $base = $la->base($LA::T::ADBASE), "can get SQL base $LA::T::ADBASE");
+
+{ # test: user
+my $oid = genid();
+
+use_ok('LATMOS::Accounts::Bases::Ad::User');
+ok($base->create_c_object('user', $oid,
+    sn => "snUser",
+    givenName => "givenNameUser",
+    ), "Can create user $oid");
+ok(my $obj = $base->get_object('user', $oid), "user $oid is actually deleted");
+is($obj->get_attributes('sn'), "snUser");
+
+my $passwd = genid();
+ok($obj->set_password($passwd), "Can set password");
+ok($base->authenticate_user($oid, $passwd), "can authenticate user");
+ok(!$base->authenticate_user($oid, '----'),
+    "don't authenticate with invalid password");
+
+ok($base->delete_object('user', $oid), "Can delete user $oid");
+ok(!$base->get_object('user', $oid), "user $oid is actually deleted");
+}
+
+
+{ # test: group
+my $oid = genid();
+
+use_ok('LATMOS::Accounts::Bases::Ad::Group');
+ok($base->create_c_object('group', $oid,
+    description => 'Group Test'
+), "Can create group $oid");
+ok(my $obj = $base->get_object('group', $oid), "group $oid is actually deleted");
+is($obj->get_attributes('description'), "Group Test");
+
+ok($base->delete_object('group', $oid), "Can delete group $oid");
+ok(!$base->get_object('group', $oid), "group $oid is actually deleted");
+}
+
Index: tags/5.2.23/LATMOS-Accounts/live-test/40_heimdal.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/live-test/40_heimdal.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/live-test/40_heimdal.t	(revision 2119)
@@ -0,0 +1,40 @@
+# -*- indent-tabs-mode: nil; tab-width: 4; -*-
+# vim:ft=perl:et:sw=4
+# $Id$
+
+use strict;
+use warnings;
+use Test::More;
+use FindBin qw($Bin);
+
+require "$Bin/common.pl";
+eval "require '$Bin/config.pl'";
+
+if ($@) {
+    plan skip_all => 'no config found' if ($@);
+} elsif (!$LA::T::HEIMDALBASE) {
+    plan skip_all => 'No heimdal base defined';
+} else {
+    plan tests => 10; # Number of test
+}
+
+use_ok('LATMOS::Accounts');
+use_ok('LATMOS::Accounts::Bases::Heimdal');
+
+ok(my $la = LATMOS::Accounts->new(undef, noacl => 1), "Can get LATMOS::Accounts");
+ok(my $base = $la->base($LA::T::HEIMDALBASE), "can get Heimdal base $LA::T::HEIMDALBASE");
+
+{ # test: user
+my $oid = genid();
+
+use_ok('LATMOS::Accounts::Bases::Heimdal::User');
+ok($base->create_c_object('user', $oid, ), "Can create user $oid");
+ok(my $obj = $base->get_object('user', $oid), "user $oid is actually deleted");
+
+my $passwd = genid();
+ok($obj->set_password($passwd), "Can set password");
+
+ok($base->delete_object('user', $oid), "Can delete user $oid");
+ok(!$base->get_object('user', $oid), "user $oid is actually deleted");
+}
+
Index: tags/5.2.23/LATMOS-Accounts/live-test/90_scripts_run.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/live-test/90_scripts_run.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/live-test/90_scripts_run.t	(revision 2119)
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use Test::More;
+
+eval "use Test::Script::Run";
+plan skip_all => 'Test::Script::Run required' if $@;
+
+plan tests => 5;
+
+run_ok('bin/la-config', "Can launch la-config");
+
+run_ok('bin/la-query',  "Can launch la-query");
+run_ok('bin/la-query', [ qw(-o group) ],  "Can launch la-query");
+run_not_ok('bin/la-query', [ qw(-o nawak) ],  "Can launch la-query");
+
+run_ok('bin/la-attributes', "Can launch la-attributes");
Index: tags/5.2.23/LATMOS-Accounts/live-test/91_scripts_run_help.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/live-test/91_scripts_run_help.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/live-test/91_scripts_run_help.t	(revision 2119)
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use Test::More;
+
+eval "use Test::Script::Run";
+plan skip_all => 'Test::Script::Run required' if $@;
+
+# Check all script can be call with --help w/o generating error
+
+my @scripts = glob('blib/script/*');
+
+plan tests => scalar(@scripts);
+
+foreach (@scripts) {
+    run_ok($_, [ qw(--help) ], "Can launch $_ --help");
+}
Index: tags/5.2.23/LATMOS-Accounts/live-test/common.pl
===================================================================
--- tags/5.2.23/LATMOS-Accounts/live-test/common.pl	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/live-test/common.pl	(revision 2119)
@@ -0,0 +1,5 @@
+# $Id$
+
+sub genid { 'aa' . join('', map { ('a' .. 'z')[rand(26)] } (0..7)) }
+
+1;
Index: tags/5.2.23/LATMOS-Accounts/live-test/config.pl.sample
===================================================================
--- tags/5.2.23/LATMOS-Accounts/live-test/config.pl.sample	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/live-test/config.pl.sample	(revision 2119)
@@ -0,0 +1,11 @@
+package LA::T;
+
+# Config file for live test
+# The name of setup base, if set live test can run
+
+our $SQLBASE = 'sql';
+our $LDAPBASE = 'ldap';
+our $ADBASE = 'ad';
+our $HEIMDALBASE = 'heimdal';
+
+1;
Index: tags/5.2.23/LATMOS-Accounts/man/man5/la-allowed-values.ini.pod
===================================================================
--- tags/5.2.23/LATMOS-Accounts/man/man5/la-allowed-values.ini.pod	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/man/man5/la-allowed-values.ini.pod	(revision 2119)
@@ -0,0 +1,40 @@
+# $Id: la-allowed-values.ini.5.pod 3157 2010-08-29 18:11:12Z nanardon $
+
+=head1 NAME
+
+la-allowed_values.ini - Attribute file control
+
+=head1 DEFAULT LOCATION
+
+F</etc/latmos-accounts/allowed_values.ini>
+
+=head1 DESCRIPTION
+
+This config is an C<ini> like file split into section. Each section describe a
+base except sections describe bellow.
+
+=head1 SECTIONS
+
+Each section is represent an attribute, name is in form C<OBJECT.ATTRIBUTE>.
+
+=head2 VARIABLES
+
+=head3 allowed
+
+The list of value allowed for this attribute
+
+=head1 EXAMPLE
+
+    [user.shell]
+    allowed=/bin/bash
+    allowed=/bin/tcsh
+    allowed=/bin/zsh
+
+=head1 AUTHOR
+
+Olivier Thauvin <olivier.thauvin@latmos.ipsl.fr>
+
+=head1 COPYRIGHT
+
+(c) LATMOS - IPSL - CNRS
+
Index: tags/5.2.23/LATMOS-Accounts/man/man5/la-sync-list.ini.pod
===================================================================
--- tags/5.2.23/LATMOS-Accounts/man/man5/la-sync-list.ini.pod	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/man/man5/la-sync-list.ini.pod	(revision 2119)
@@ -0,0 +1,86 @@
+# $Id: la-sync-list.ini.5.pod 3152 2010-08-29 15:11:18Z nanardon $
+
+=head1 NAME
+
+la-sync-list.ini - Configuration files buildlistes process
+
+=head1 DEFAULT LOCATION
+
+F</etc/latmos-accounts/la-sync-list.ini>
+
+=head1 DESCRIPTION
+
+This config is an C<ini> like file split into section. Each section describe a
+base except sections describe bellow.
+
+=head1 [_default_] SECTION
+
+This section contains global configuration variables
+
+=head2 VARIABLES
+
+=head3 object
+
+Objects to use to find people to subscribe.
+
+=head3 destdir
+
+The directory where file must be written.
+
+=head3 cmd
+
+A command to run for each list once list is build. C<%%> is replaced by list
+name.
+
+=head3 pre
+
+A command to run before processing. C<DIRECTORY> envirronement variable contains
+the L<destdir> variable.
+
+=head3 post
+
+A command to run after processing. C<LA_DIRECTORY> envirronement variable contains
+the L<destdir> variable.
+
+=head3 post_file
+
+A command to run after each file processing. C<LA_DIRECTORY> envirronement variable contains
+the L<destdir> variable, C<LA_OUTPUT_FILE> contains the fresh written file filename.
+
+=head1 OTHERS SECTIONS
+
+Each other section describe a list to build
+
+=head2 VARIABLES
+
+=head3 filter
+
+The search filter to apply to find people to subscribe
+
+=head3 excludefilter
+
+A search filter for which results will be excluded
+
+=head2 addtolist
+
+A list of mail to add anyway to the results
+
+=head2 FILTER
+
+Multiple filter can be specify by adding a number to C<filter> variable.
+Example:
+
+    filter=company=LATMOS
+    filter=mail=*
+    filter2=mail=*
+    filter2=contratType=heberges
+
+
+=head1 AUTHOR
+
+Olivier Thauvin <olivier.thauvin@latmos.ipsl.fr>
+
+=head1 COPYRIGHT
+
+(c) LATMOS - IPSL - CNRS
+
Index: tags/5.2.23/LATMOS-Accounts/man/man5/la-sync-manager.ini.pod
===================================================================
--- tags/5.2.23/LATMOS-Accounts/man/man5/la-sync-manager.ini.pod	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/man/man5/la-sync-manager.ini.pod	(revision 2119)
@@ -0,0 +1,54 @@
+# $Id: la-sync-manager.ini.5.pod 3157 2010-08-29 18:11:12Z nanardon $
+
+=head1 NAME
+
+la-sync-manager.ini - Control task la-sync-manager have to run
+
+=head1 DEFAULT LOCATION
+
+F</etc/latmos-accounts/la-sync-manager.ini>
+
+=head1 DESCRIPTION
+
+This config is an C<ini> like file split into section. Each section describe a
+module to run and its options.
+
+=head1 EXAMPLE
+
+    [reseau]
+    type=buildnet
+
+    [listes]
+    type=buildlistes
+
+    [Stat]
+    type=Stats
+
+    [employment]
+    type=Employment
+
+    [unexexpired]
+    type=UnexportExpired
+
+    [Expiredaliases]
+    type=Expiredaliases
+    destination=|/bin/true
+
+=head1 GLOBALS MODULE OPTIONS
+
+=head2 type
+
+The module type to run, see C<LATMOS::Accounts::Task::*.pm>
+
+=head2 disable
+
+If set, the module is not run.
+
+=head1 AUTHOR
+
+Olivier Thauvin <olivier.thauvin@latmos.ipsl.fr>
+
+=head1 COPYRIGHT
+
+(c) LATMOS - IPSL - CNRS
+
Index: tags/5.2.23/LATMOS-Accounts/man/man5/latmos-accounts.ini.pod
===================================================================
--- tags/5.2.23/LATMOS-Accounts/man/man5/latmos-accounts.ini.pod	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/man/man5/latmos-accounts.ini.pod	(revision 2119)
@@ -0,0 +1,201 @@
+# $Id: latmos-accounts.ini.5.pod 3186 2010-09-01 08:10:38Z nanardon $
+
+=head1 NAME
+
+latmos-accounts.ini - Main configuration file for latmos-account.
+
+=head1 DEFAULT LOCATION
+
+F</etc/latmos-accounts/latmos-accounts.ini>
+
+=head1 DESCRIPTION
+
+This config is an C<ini> like file split into section. Each section describe a
+base except sections describe bellow.
+
+=head1 [_default_] SECTION
+
+This section contains global configuration variables
+
+=head2 VARIABLES
+
+=head3 base
+
+The top level base to use by default to modify informations. The base must
+exists as a section in the configuration
+
+=head3 sync
+
+The default synchronisation to run. The synchronisation must exists as a section
+in configuration.
+
+=head3 state_dir
+
+The directory where data files will be stored
+
+=head3 smtp
+
+The SMTP server to use to send mails
+
+=head3 mailFrom
+
+The identity to use in FROM field when sending mail
+
+=head3 expire_summary_to
+
+When set, this email address will receive a summary about account expiring soon
+
+=head3 mailSubject
+
+This parameter is dedicate to set the company or institute name. It is used to
+build the subject of mail send by application.
+
+=head3 crypt_method
+
+Specify the C<crypt()> algorythm to use to encrypt password when the work is
+done on application side. Can be DES, MD5, SHA-256 or SHA-512, default to MD5.
+
+Can be specified per base.
+
+=head3 cracklib_dictionnary
+
+The path to a specific dictionnary formated for cracklib library
+
+=head2 [_defattr_] SECTION
+
+Contains value assigned at object creation if the value is not specified.
+Each variable is in form C<object.attributes>.
+
+Some value can be specify for more complex auto completion:
+
+=head3 user.homebase
+
+This value is used a base directory for UNIX user home. The login is append to
+end of the value (preceded by a C</>).
+
+=head3 user.maildomain
+
+If exists set the mail attribute in form C<sn.givenName@maildomain>.
+
+=head2 [_network_]
+
+This section is used by buildnet tools, to generate network config file (DNS
+zone and dhcp).
+
+=head3 template_dir
+
+The directory containing template files
+
+=head3 output_dir
+
+The directory containing results files
+
+=head3 pre
+
+A command to execute before processing all zones
+
+=head3 post
+
+A command to run after procession all zones
+
+=head2 post_zone
+
+A command which will be run after each zone build
+
+=head2 maillog
+
+If set, must contains an email address where error will be sent in batch mode.
+
+=head2 checkzone
+
+If set generated DNS zones will be first written in a temporary files and
+checked by C<checkzone> utility.
+
+If the test fail the zone is not generated and temporary file not removed for
+analysis.
+
+=head2 named-checkzone
+
+The binary location of named-checkzone, default is C</usr/sbin/named-checkzone>.
+
+=head2 BASES SECTIONS
+
+TODO
+
+=head2 SYNCHRO SECTIONS
+
+Each synchronisation definition is identified by a section, the section name is
+prefixed by C<sync:>.
+
+=head3 from
+
+The base to use as source
+
+=head3 to
+
+The bases to synchronize, multiples bases can be specified
+
+=head3 pre
+
+A script to run before processing, if it failed, the synchronisation is not
+performed
+
+=head3 post
+
+A script to run after processing, if it failed, the synchronisation is not
+recorded as done.
+
+=head3 unexported
+
+When set, unexported object are synchronised, usefull for base supporting this
+feature (SQL only at time)
+
+=head3 noDelete
+
+Setting this parameter will make the syncronisation not deleting object in
+destination bases.
+
+Setting the C<noDelete.BASE> where C<BASE> is the name of an synchronized base
+will make this synchronisation not deleting any object into this base,
+
+Setting the C<noDelete.BASE.OTYPE> will make the synchronisation not deleting
+object type C<OTYPE> no delete into the base named C<BASE>.
+
+Example:
+
+    noDelete.MyLdap.user = yes
+
+=head3 filtering object propagation
+
+It is possible to filter the objects you want to propagate into peer base.
+
+The filter to apply must be set into the parameter named
+C<filter.BASENAME.OTYPE>, where C<BASENAME> is the name of the destination
+base and C<OTYPE> the object type to filter.
+
+For example to push into C<ldap> base only user being into group C<unix>:
+
+  filter.ldap.user = memberOf=unix
+
+
+=head3 Deleting filtered object
+
+By default filtered object will not be deleted to the destination base, neither
+touched.
+
+Setting option in form C<deletefiltered.BASENAME.OTYPE> to true will allow
+deletion. This option has effects only if filtering objects is enabled.
+
+=head1 SEE ALSO
+
+la-allowed-values.ini(5),
+la-sync-list.ini(5) 
+
+=head1 AUTHOR
+
+Olivier Thauvin <olivier.thauvin@latmos.ipsl.fr>
+
+=head1 COPYRIGHT
+
+(c) LATMOS - IPSL - CNRS
+
Index: tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-ad.pod
===================================================================
--- tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-ad.pod	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-ad.pod	(revision 2119)
@@ -0,0 +1,87 @@
+# $Id$
+
+=head1 DESCRIPTION
+
+The C<ad> base support C<Active Directory> system from C<Microsoft> using
+C<ldap> protocol.
+
+It has been succefully test on C<Windows 2008> and C<Windows 2008r2>.
+
+=head1 SPECIFIC SETUP PARAMTERS
+
+=head2 domain
+
+The domain name of this active directory base
+
+=head2 server
+
+Optional, specify the server to contact to access to this ldap base.
+
+If not set, a DNS lookup is performed to find the list of available servers
+
+=head2 login
+
+The dn to use to connect to the ldap
+
+=head2 password
+
+The password to cuse to connect to ldap server
+
+=head2 ssl
+
+If set, try to connect using ssl
+
+Notice using ssl or tls is mandatory to be able to setup password.
+
+=head2 tls
+
+If set start tls encryption on standard ldap port.
+
+C<ssl> parameter must not set in this case.
+
+=head2 user_container
+
+The dn subpart of the container where user are located and will be created
+
+=head2 group_container
+
+The dn subpart of the container where groups are located and will be created
+
+=head2 user_key_attribute, group_key_attribute
+
+Specify the attribute to use to uniquely identify an object. The default is
+C<cn>.
+
+=head2 user_dn_attribute, group_dn_attribute
+
+Specify the attribute to use forge the C<dn> of new object. The default is
+C<cn>.
+
+=head1 FAQ
+
+=head2 Failure when chnaging password
+
+When I try to sync/change a user password I get:
+
+    0000052D: SvcErr: DSID-031A120C, problem 5003 (WILL_NOT_PERFORM), data 0
+
+The meaning of this error is "the password does not pass the quality test".
+
+Active directory has by default a strong password policy including the password
+lenght, the variety of caracters, etc...
+
+The C<LATMOS::Accounts> system have itself a password quality test but it does
+not have the same rules than an active directory, so a password accept on one
+side can be reject on the other.
+
+To change the password policy on active directory:
+
+=over 4
+
+=item open the Group Policy Console (Administration Tools)
+
+=item Find the C<default domain policy> at top level of the domain
+
+=item change parameter under system, Windows, Security parameters
+
+=back
Index: tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-ldap.pod
===================================================================
--- tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-ldap.pod	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-ldap.pod	(revision 2119)
@@ -0,0 +1,50 @@
+# $Id$
+
+=head1 DESCRIPTION
+
+=head1 SPECIFIC SETUP PARAMTERS
+
+=head2 server
+
+The server name to use to access to ldap base
+
+=head2 topdn
+
+The base dn of the ldap
+
+=head2 login
+
+The dn to use to connect to the ldap
+
+=head2 password
+
+The password to cuse to connect to ldap server
+
+=head2 ssl
+
+If set, try to connect using ssl
+
+=head2 tls
+
+If set start tls encryption on standard ldap port.
+
+C<ssl> parameter must not set in this case.
+
+=head2 user_container
+
+The dn subpart of the container where user are located and will be created
+
+=head2 group_container
+
+The dn subpart of the container where groups are located and will be created
+
+=head2 user_key_attribute, group_key_attribute
+
+Specify the attribute to use to uniquely identify an object. The default is
+C<cn>.
+
+=head2 user_dn_attribute, group_dn_attribute
+
+Specify the attribute to use forge the C<dn> of new object. The default is
+C<cn>.
+
Index: tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-sql.pod
===================================================================
--- tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-sql.pod	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-sql.pod	(revision 2119)
@@ -0,0 +1,331 @@
+# $Id$
+
+=head1 DESCRIPTION
+
+=head1 SPECIFIC SETUP PARAMTERS
+
+=head2 db_conn
+
+The C<libpq> connection parameters, eg a semin colon separated paramaters
+containing the server, the database name, user and password, etc...
+
+=head2 no_pg_utf8
+
+If set disable utf8 flags from postgresql. You can try this parameter if you
+have issue with non ascii value from database
+
+=head2 remove_old_dpmt
+
+By default when the department is changed on a user account it remains in the
+department group as a secondary department.
+
+Setting C<remove_old_dpmt> to true in the config will force user removal from
+the group when department is changed.
+The user can still be added back later.
+
+=head1 FEATURES
+
+=head2 Object Aliases
+
+It is possible to create object being simple alias to another, like symbolic on
+UNIX filesystem or mail alias.
+
+Thoses aliases objects are always resolved when propagating into other base.
+The referenced object can be easilly change and all the data related will be
+propagated.
+
+Only alias for object type C<User> are supported at time.
+
+=head3 Usage Example:
+
+The typical exemple is for attribute C<manager> or attribute C<managedBy>.
+Instead setting them to C<John> you can create an alias C<Director> and when
+C<John> leave just change alias reference to C<Bill>.
+
+=head2 Network managment
+
+Link::Accounts can build automatically some part of your DNS or DHCP
+configuration.
+
+To do this you have to create a C<netzone> object. Such object need a type:
+
+=over 4
+
+=item dns: to build a DNS zone for classic domain
+
+=item reverse: for reverse IP address (168.192.in-addr.arpa)
+
+=item dhcp: ISC dscpd configuration for fixed address
+
+=item puppet: puppet configuration
+
+=back
+
+The way it works is quite simple, each zone will make the code to write a file
+you can include in your server configuration. The match is done by looking the
+zone IP address masks and the host IPs.
+
+For example someone having a zone named C<private.mydomain.com> having masks
+C<192.168.5.0/24>, and having an host named C<foo> with IP C<192.168.5.3> and
+another host C<bar> with IP C<192.168.13.78>. The zone built will look likes:
+
+    foo IN A 192.168.5.3
+
+As you can see this DNS zone is not valid: the goal of such feature is to make
+the repetive work for us, not to manage the full zone (even such feature could
+be possible). The repetitive work is declaring the hundred computers our users
+have.
+
+The output will be happend to a template have the name of the zone suffixed by
+C<.in>. You can put in this template evering about the zone declaration (SOA,
+NS, TXT...).
+
+=head2 User endcircuit
+
+The C<endcircuit> attribute contain the deadline for people to make
+admnistrative task when starting to work.
+If set this attribute take precedence to C<expire> attribute for computed
+attributes (C<accountExpires> for Active Directory).
+
+=head2 User Employment
+
+The employment object allow you to set time when you're user have a status. This
+allow through 'Employment' module for la-sync-manager to automate changes.
+
+The synchronized attributes are:
+
+=over 4
+
+=item C<company>
+
+=item C<employer>
+
+=item C<department>
+
+=item C<contratType>
+
+=item C<managerContact>
+
+=item C<expire>
+
+=item C<endcircuit>
+
+=back
+
+To avoid error when modifying user direclty when you're using employment those
+attribute become on user's side become read-only once an employment exists.
+
+You can change this beaviour using C<employment_lock_user> parameter:
+
+By default it is impossible to modify or create past employment. This behavior
+can be changed by settings C<allow_pasted_employment> parameter in base
+configuration.
+
+=over 4
+
+=item any (default)
+
+Any existing employment lock those attribute, you must
+create another employment to change user status or delete all employements for
+this user.
+
+=item always
+
+The user's attribute are always locked
+
+=item never
+
+The user's attribute are always locked
+
+=item active
+
+Thoses attributes are locked is any employment are still active (ie unfinished
+or coming later).
+
+=item attribute=value
+
+Thoses attributes are read-only if the C<attribute> given contains C<value>,
+C<*> allow to match any value.
+
+=back
+
+When active users become out of any employment all managed attribute are unset
+(except the expire attribute).
+
+A default value for each of this attribute can be set in configuration using
+parameter in form C<unemployment.ATTRIBUTE>. For example
+C<unemployment.contratType=external> will set any C<contratType> to C<external>
+when no employment apply to user anymore.
+
+Only active accounts are modified in this way.
+
+=head3 User endEmployment
+
+This attribute compute the next day the user will leave the company according
+the employment object registered.
+
+The parameter C<employment_delay> give the number of days to ignore when a hole
+exists between two employment.
+
+If no employment are found, if set the date given in C<unemployed_expire>
+database parameter is returned.
+
+=head3 User endStrictEmployment
+
+This attribute compute the next day the user will leave the company according
+the employment object registered.
+
+It does not take C<employment_delay> parameter into account.
+
+If no employment are found, if set the date given in C<unemployed_expire>
+database parameter is returned.
+
+=head3 User endLastEmployment
+
+This attribute return the very last end of all registered employment fr this
+user.
+
+=head3 User endCurrentEmployment
+
+The end of the employment matching current date.
+
+=head3 Account Expiration
+
+When using employment, account expiration are set to match employment. By
+default the expiration is set to C<endEmployment> value.
+
+This behaviour can be changed by setting C<expireOn> parameter into base
+definition:
+
+=over 4
+
+=item any of endCurrentEmployment, endEmployment, endStrictEmployment, endLastEmployment
+
+=item never
+
+The expire date is left unchanged and must managed manually.
+
+=back
+
+=head2 Group AutoMemberFilter
+
+Group objects contains users members by setting either C<members> or
+C<memberUID> attributes.
+
+Sometimes it can be usefull to have group automatically populated by arbitrary
+rules.
+
+This is possible by setting a filter in the C<autoMemberFilter> attribute,
+The filter format is the same the one used by L<la-search>, the attribute is
+multivaluable.
+
+So for example one can create an account automatically a group containing people
+having "Olivier" as first name:
+
+    autoMemberFilter: givenBame=Olivier
+
+A probably more usefull example is a group containing people from two others
+groups:
+
+    autoMemberFilter: memberOf=group1
+    autoMemberFilter: memberOf=group2
+
+The  C<members> or C<memberUID> attribute becomes read-only attribute once
+C<autoMemberFilter> attribute is set.
+
+=head2 Aliases AutoMemberFilter
+
+This attribute allow to create automatics dynamics aliases according filter
+rules exactly like L<Group AutoMemberFilter> works.
+
+The C<forward> attributes is automatically set with email address of selected
+user, user w/o email address are ignored.
+
+=head2 Group AutoFromSutype
+
+Group object can be tagged with the C<sutype> attribute.
+
+When C<autoFromSutype> is set the group member will be computed from member of
+all groups having C<sutype> set this value.
+
+The goal of this attribute is to setup magic group like with the
+C<autoMemberFilter> but working even a new group is created.
+
+=head2 Aliases AutoFromSutype
+
+This attribute allow to create automatics dynamics aliases according filter
+rules exactly like L<Group AutoFromSutype> works.
+
+The C<forward> attributes is automatically set with email address of selected
+user, user w/o email address are ignored.
+
+=head2 Statistics
+
+The application provide some statitics tools but they are only based on the
+current data inside the database and are unable to track delete data.
+
+To keep some mesurement you must use C<stat> objects to describe the data you
+want to track, and enable in L<la-sync-manager.ini> the C<Stats> module.
+
+Each attribute of C<Stat> object describe how data must but compute before being
+stored.
+
+=head3 Stat object Attributes
+
+=head4 description
+
+A label about this statistics object
+
+=head4 display
+
+IF set the statistic appear in the menu of the web interface
+
+=head4 otype
+
+The object type this stat is tracking, must be a supported object type
+
+=head4 filter
+
+One or multiple filter to limit the objects taking into account
+
+=head4 attribute
+
+The attribute fetch to compute data
+
+=head4 refFilter
+
+When the attribute reference another type of object this setting allow to
+filter to the matching referenced object.
+
+=head4 refAll
+
+When the attribute reference another type of object non exiting objects in
+the results appear as 0, otherwise they are ignored.
+
+=head4 aggregateFunction
+
+An optionnal operation to do on the data:
+
+=over 4
+
+=item sum
+
+Make the sum of the result per item
+
+=item avg
+
+Make the average of the result per item
+
+=item count
+
+Count the number of item return
+
+=back
+
+=head4 delay
+
+The number of day between two run
+
+=head4 retention
+
+If set, the duration in days after which stats value must deleted
Index: tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-unix.pod
===================================================================
--- tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-unix.pod	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-unix.pod	(revision 2119)
@@ -0,0 +1,67 @@
+# $Id$
+
+=head1 DESCRIPTION
+
+Unix base type support standard unix users and groups base, eg C<password> and
+C<group> file normally located in C</etc>.
+
+This base support has written to support C<NIS> database, generated over this
+files.
+
+Managing the system file with this module is at your own risk.
+
+Unlike other base support, this module need direct read and write permission to
+filesystem. Ensure tools and daemon are run using ocrrect user.
+
+=head1 SPECIFIC SETUP PARAMTERS
+
+=head2 directory
+
+The directory where files are located. If not specified F</etc> is assumed.
+
+=head2 use_shadow
+
+If set to true, F<shadow> and F<gshadow> are generated, password in F<passwd>
+file are replace by C<x>. This also mean attributes stored in shadow files are
+supported.
+
+=head2 nis_overflow 
+
+Some version of C<NIS> base dont support line longer than 4096 bytes, which
+happend if you have lot of people in a specific groups.
+
+This parameter provide ways to avoid this problem (notice it concern only the
+F<group> file.
+
+=over 4
+
+=item <unset>
+
+No specific action is taken, line is writen as is into the output file.
+
+=item kill
+
+An error is report and the line is simply remove from the output. Information
+will be missing but at least the C<NIS> database will still works
+
+=item truncate
+
+The member list is truncate to not exceed the 4096 byte limit (member list is
+sorted alphabetically).
+
+=item split
+
+Group members are split into severals differents groups having same C<GID>. This
+mean for last users checking they are in the groups will fails but permission
+checks on filesystem will still apply correctly.
+
+The ouput look like this:
+
+    group:500:x:aa,bb,cc
+    group_1:500:x,dd,ee,ff
+
+=item encoding
+
+Specify the encoding of the file. Default is C<iso-8859-15>.
+
+=back
Index: tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-zimbra.pod
===================================================================
--- tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-zimbra.pod	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-base-zimbra.pod	(revision 2119)
@@ -0,0 +1,53 @@
+# $Id$
+
+=head1 DESCRIPTION
+
+The C<Zimbra> driver provide a way to create and delete account using Zimbra
+Soap interface
+
+=head1 SPECIFIC SETUP PARAMTERS
+
+=head2 url
+
+The url to the zmstore server:
+
+    Example: url=https://zmstore.domain:7071/service/admin/soap
+
+=head2 login
+
+The administrator login to use
+
+=head2 password
+
+The password of the administrator account
+
+=head2 domain
+
+The Zimbra domain to manage, account in other domains are not touched
+
+=head1 ATTRIBUTES
+
+=head2 USER
+
+=head3 uid
+
+The C<uid> attribute is used as to identify account then to identify accross
+others bases
+
+=head3 zimbraMailAliases
+
+This attribute is declared as a read-only attribute. Aliases can be setup
+through the C<aliases> attribute. Unlike C<zimbraMailAliases> it will accept
+domain-less aliases, it will add the zimbra domain if need.
+
+=head3 zimbraCOSId
+
+It must contains the class of service B<ID> (not the name) to apply the to the account.
+
+This attribute is not really managed but a default value can be applied from the
+configuration:
+
+Example:
+
+    [_defattr_]
+    user.zimbraCOSId = bcb918ad-d2e0-4adf-8329-c46c65bbc9d6
Index: tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-changes.pod
===================================================================
--- tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-changes.pod	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-changes.pod	(revision 2119)
@@ -0,0 +1,52 @@
+=head1 3.0.1
+
+    - allow to specify key and dn attribute for LDAP based storage
+    - add support for various encoding for UNIX file
+    - add description attribute to aliases and revaliases object
+    - add user attributes to nethost to store poeple using the computer if it is
+      not the owner (make difference between user and admin).
+    - Rename la-crypt-passwd, la-exchange-ip, la-rev to la-sql... for coherency
+    - don't support single config file anymore
+    - add managedAlsoBy attribute to group: managedBy is a single value
+      attribute, this new attributes allow to specify co-manager.
+
+=head1 3.0.0
+
+    - hande IPv6 addresses
+    - support kerberos database (heidmal)
+
+=head1 2.0.0
+
+    - attributes are now object with possible properties:
+        * hide: attributes is still availlable but not listed
+        * ro: attributes can only be read
+        * can_values: list of possible values, other are rejected 
+        * mandatory: cannot be empty or not set
+        * reference: the attribute point to this object type
+
+    - add support of network management
+        * object nethost and netzone
+        * BuildNet objects
+        * associated tools
+
+    - la-sync-manager can now manage multiples task: each task is an object
+      and la-sync-manager.ini control task to run
+
+    - all configurations files are now located in /etc/latmos-accounts and all
+      files have a fixed name. --config is no longer the main configuration file
+      but the directory containing theses files
+
+    - configuration files are now more or less documented (man section 5)
+
+    - exported attributes is replace by unexported attributes (reverse meaning),
+      unlike exported which is true/false, unexported is set or not.
+
+    - some optimization, especially for SQL base
+
+    - add la-cli tools: interactive command line with completion
+
+=head1 0.01
+
+	- original version; created by h2xs 1.23 with options
+		-n LATMOS::Accounts --omit-XS --omit-autoload
+
Index: tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-faq.pod
===================================================================
--- tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-faq.pod	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts-faq.pod	(revision 2119)
@@ -0,0 +1,47 @@
+# $Id$
+
+=head1 INTRODUCTION
+
+The goal of this docmuentation is to give soo clue about how to manage usuall
+issue with this software.
+
+=head1 FAQ
+
+=head2 Denying to locked user to login using SSH with key
+
+There is no way on linux to deny to expired or locked user to login using ssh
+and ssh key because sshd simply skip the authentification step on PAM and both
+checks are done here.
+
+On way could be to change the shell, this is what is done for standard C<passwd>
+database but you immediatelly feel this method is ugly and complex to code.
+
+After some wonder about how to do here the solution found as the best.
+
+In C<sshd_config> you can define users and groups not allow to login, and
+because this is managed by C<sshd> itself it will work whatever the login method
+is used.
+
+In the L<LATMOS::Accounts> database create groups to match user's states one for
+locked user and one for expired users (unexported user don't exists anymore, no
+need to filter them).
+
+Now add to both an C<autoMemberFilter> to match thoses users:
+
+For locked users:
+    autoMemberFilter: unexported=0
+    autoMemberFilter: locked=*
+
+For expired users:
+    autoMemberFilter: unexported=0
+    autoMemberFilter: expired=*
+
+Now add to you C<sshd_config>:
+
+    DenyGroups expired locked
+
+where C<expired> and C<locked> are the name of both groups.
+
+You're done, both groups will be update automatically according user's status
+and C<sshd> will reject them.
+
Index: tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts.pod
===================================================================
--- tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts.pod	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/man/man8/latmos-accounts.pod	(revision 2119)
@@ -0,0 +1,340 @@
+# $Id$
+
+=head1 NAME
+
+    Link::Accounts - A multiple accounts base management and synchronisation
+    system
+
+=head1 GENERALITY
+
+=head2 HISTORY
+
+Link::Accounts is born with the need to have same set of groups and users in
+multiples accounts bases:
+
+=over 4
+
+=item OpenLDAP
+
+Standard ldap base w/o any change on basis schema
+
+=item Active Directory
+
+The domain management system for windows. Active Directory is a solution
+including LDAP, Kerberos, Smb protocol and a graphic interface to configure it.
+
+=item Unix file
+
+Standard users base under Unix system, also used for C<NIS>/C<YP> system.
+
+=item Kerberos/Heimdal
+
+Remote access to Kerberos base.
+
+=back
+
+=head2 WORKFLOW
+
+C<Link::Accounts> is made of three components:
+
+=over 4
+
+=item L</Bases>
+
+=item L</Objects>
+
+=item L</Attributes>
+
+=back
+
+=head3 Bases
+
+A base module provide the common way to access a set of data. Usually a base is
+accessed remotely trought a network connection but always.
+
+In an ideal world, all base can be synchronised over another one.
+
+Each data inside a base is represented as objects of differents type. The most
+common type are for sure C<user> and C<group>.
+
+=head3 Objects
+
+Inside a base the objects is the unbreakble subset of data.
+
+Each object are identified uniquely by both a type and an identifier on the
+C<LATMOS::Accounts> side. This mean two object of different type can have the
+same id.
+
+On the other hand the identifier must allow to identify uniquelly the object
+inside the base.
+
+For example in C</Unix file> base each line of F<passwd> is a C<user> object and
+the login is the uniq identifier.
+
+The couple object type/identifier must also allow to find common objects through
+differents bases.
+
+=head3 Attributes
+
+The attributes is the basic data storage for an object.
+
+Each attribute have a name and normally a specific usage, even through base.
+
+Attributes may have different behavior, depending their definition and base
+constraint:
+
+=over 4
+
+=item single or multiple value
+
+=item limited possible values
+
+=item reference to others objects
+
+=back
+
+=head1 CONFIGURATION
+
+All configuration files listed bellow must be stored in the same directory.
+
+By default this directory is F</etc/latmos-accounts>.
+
+It can be overload by setting envirronment variable C<LA_CONFIG>.
+
+=head2 Configuration files list
+
+=over 4
+
+=item latmos-accounts.ini
+
+Bases and synchronisations definitions.
+
+See L<latmos-accounts.ini>
+
+=item la-allowed-values.ini
+
+Attributes values allowed.
+
+See L<la-allowed-values.ini>
+
+=item la-sync-manager.ini
+
+The setup of L<la-sync-manager>, the daemon in charge of pushing values from
+primary base to others.
+
+See L<la-sync-manager.ini>
+
+=item la-acls.ini
+
+Access list configuration, used by Web application
+
+See L<la-acls.ini>
+
+=item la-sync-list.ini
+
+Configuration of mailing synchronisation module
+
+See L<la-sync-list.ini>
+
+=back
+
+=head1 INPUT FILE
+
+The input file is a list of attribute and value separate by a semi-colon.
+Multi-values attributes must be repeated for each value.
+Attributes not listed are left untouched.
+
+Example:
+
+    sn: Myname
+    givenName: Myfirstname
+
+Some object have related objects, for example C<user> have multiple C<address>
+and C<employment>. It can be usefull to create those object in same time the
+main object.
+
+At creation the related object attributes can given using attribute in form
+C<OTYPE.ATTRIBUTE>.
+
+Example:
+
+    sn: Myname
+    givenName: Myfirstname
+    address.streetAddress: 5th Avenue, 123
+
+Multiple objects of same type can be given a key between C<[]>, this key may
+use any letter C<a-z>, C<A-Z> or number C<0-9>.
+
+    sn: Myname
+    givenName: Myfirstname
+    address[0].streetAddress: 5th Avenue, 123
+    address[aa].streetAddress: 5th Avenue, 123
+
+=head1 TOOLS LIST
+
+The tools listed bellow are availlable to administrate the C<Link::Accounts>
+system.
+
+They are low level tools and are designed to be used system administrator, not
+end user (unlike the web interface).
+
+=head2 Configuration Tools
+
+=over 4
+
+=item la-config
+
+Display configuration information.
+
+L<la-config>
+
+=item la-attributes
+
+Display supported object and attributes.
+
+L<la-attributes>
+
+=item la-log-test
+
+Send message using log functions (for testing purpose)
+
+L<la-log-test>
+
+=item la-acls
+
+Check validity of ACL file (eg L<la-acls.ini>).
+
+See: L<la-acls>
+
+=item la-qacls
+
+Test ACLs permission for given user over given object.
+
+See: L<la-qacls>
+
+=back
+
+=head2 Base Content Tools
+
+=over 4
+
+=item la-cli
+
+Interactive command line interface, include online help, completion, etc...
+
+See L<la-cli>
+
+=item la-create
+
+Create an object into main base.
+
+See L<la-create>
+
+=item la-delete
+
+Delete an object from main base
+
+See L<la-delete>
+
+=item la-edit
+
+Modify object into main base.
+
+See L<la-edit>
+
+=item L<la-expired-reminder>
+
+
+=item L<la-graph.pl>
+
+=item L<la-group>
+
+=item L<la-guser>
+
+=item L<la-passwd>
+
+Change the password of users.
+
+=item L<la-query>
+
+The basic tools to query any database
+
+=item L<la-rename>
+
+Allow to rename an object in all configured base simultaneously, then avoiding
+a deletion  and a creation potentially destructive in some base.
+
+To use carrefully
+
+=item L<la-search>
+
+Search object into base.
+
+=item L<la-sync>
+
+=item L<la-sync-manager>
+
+=item L<la-sync-process>
+
+=item L<la-warn-expire>
+
+=item L<la-test-mail>
+
+=back
+
+=head2 SQL Base Tools
+
+=head3 Common tools
+
+=over 4
+
+=item L<la-sql-freeip>
+
+Return an unallocated IP address from the given DHCP zone.
+
+=back
+
+=head3 Maintenance tools
+
+=over 4
+
+=item L<la-sql-rev>
+
+Give the latest internal revision of the base. The revision is a counter
+increase when database is changed.
+
+=item L<la-sql-upgrade>
+
+Upgrade the schema of the SQL database
+
+=item L<la-sql-crypt-passwd>
+
+Tools to managed the reversible encryptage of passaword.
+
+=item L<la-rename-host>
+
+Rename an network host
+
+=item L<la-sql-exchange-hostname>
+
+Exchange the name between two hosts.
+
+=item L<la-sql-exchange-ip>
+
+Exchange two ip address between different hosts.
+
+=item L<la-sql-find-expired>
+
+=item L<la-sql-edit-form>
+
+=item L<la-sql-list-request>
+
+=item L<la-sql-valid-request>
+
+=back
+
+=head1 BUGS
+
+=head1 AUTHOR
+
+Olivier Thauvin <olivier.thauvin@latmos.ipsl.fr>
Index: tags/5.2.23/LATMOS-Accounts/patchset/no_useless_load.patch
===================================================================
--- tags/5.2.23/LATMOS-Accounts/patchset/no_useless_load.patch	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/patchset/no_useless_load.patch	(revision 2119)
@@ -0,0 +1,65 @@
+This patch avoid load() on base to be call several time. Bases are supposed to
+be load by end point script and not lazilly to check access is OK.
+The problem with this patch is we need to check all tools to verify load() is
+really called :\ Dangerous at time.
+Index: lib/LATMOS/Accounts/Synchro.pm
+===================================================================
+--- lib/LATMOS/Accounts/Synchro.pm	(rÃ©vision 1531)
++++ lib/LATMOS/Accounts/Synchro.pm	(copie de travail)
+@@ -75,7 +75,9 @@
+     foreach (ref($to) eq 'ARRAY' ? @{ $to || []} : ($to)) {
+         push(@{$self->{to}}, $_);
+     }
+-    bless($self, $class)
++    bless($self, $class);
++    $self->from->load or return;
++    return $self;
+ }
+ 
+ sub name {
+@@ -103,6 +105,7 @@
+     my ($self) = @_;
+     my @loaded;
+     my $unloaded = 0;
++
+     foreach ($self->to) {
+         if($_->load) {
+             push(@loaded, $_);
+@@ -117,10 +120,10 @@
+ 
+ sub enter_synch_mode {
+     my ($self) = @_;
+-    $self->from->load or return;
++    # $self->from->load or return;
+     # if any cannot be loaded, return,
+     # TODO we need a way to force if some still can be sync
+-    $self->load_dest and return;
++
+     my %state = ();
+     $state{$self->from->label} = $self->from->wexported(0);
+     foreach ($self->to) {
+Index: lib/LATMOS/Accounts.pm
+===================================================================
+--- lib/LATMOS/Accounts.pm	(rÃ©vision 1531)
++++ lib/LATMOS/Accounts.pm	(copie de travail)
+@@ -156,7 +156,6 @@
+         la_log(LA_WARN, "Cannot instanciate base $section ($type)");
+         return;
+     };
+-    $base->load or return;
+     $base;
+ }
+ 
+@@ -234,6 +233,12 @@
+         );
+     }
+ 
++    my @unload = grep { ! $_->load } @obases;
++    if (@unload) {
++        la_log(LA_ERR, "Cannot load base part of syncaccess %s", join(', ', map
++                { $_->label } @unload));
++        return;
++    }
+     LATMOS::Accounts::SynchAccess->new([ @obases ]);
+ }
+ 
Index: tags/5.2.23/LATMOS-Accounts/po/fr.po
===================================================================
--- tags/5.2.23/LATMOS-Accounts/po/fr.po	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/po/fr.po	(revision 2119)
@@ -0,0 +1,570 @@
+# This file is distributed under the same license as the PACKAGE package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 1\n"
+"POT-Creation-Date: 2015-12-28 17:00+01\n"
+"Last-Translator: Olivier Thauvin <olivier.thauvin@latmos.ipsl.fr\n"
+"Language: FR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. ($Dd)
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:110
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:496
+msgid "%d days"
+msgstr "%d jours"
+
+#. ($Dm)
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:109
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:495
+msgid "%d months"
+msgstr "%d mois"
+
+#. ($Dy)
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:108
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:494
+msgid "%d years"
+msgstr "%d ans"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1405
+msgid "Account Request id"
+msgstr "Demande de compte"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1191
+msgid "Active"
+msgstr "Actif"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:111
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:670
+msgid "Aliases"
+msgstr "Alias"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:383
+msgid "Arrival"
+msgstr "ArrivÃ©e"
+
+#. ($attribute)
+#: lib/LATMOS/Accounts/Bases/Objects.pm:668
+#: lib/LATMOS/Accounts/Bases/Objects.pm:705
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:850
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:915
+msgid "Attribute %s is not readable"
+msgstr "Attribut %s n'est pas lisible"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:173
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:152
+msgid "Automatics filters"
+msgstr "Filtres automatiques"
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:205
+msgid "Cache for oalias data"
+msgstr "Cache pour les alias d'objet"
+
+#: lib/LATMOS/Accounts/Bases.pm:483
+msgid "Cannot create %s object with empty id"
+msgstr "Ne peut crÃ©Ã© l'object %s avec un identifiant vide"
+
+#. ($base)
+#: bin/la-load-csv:59 bin/la-query:117 bin/la-renewEmp:62
+msgid "Cannot load base %s\n"
+msgstr "Ne peut charger la base %s\n"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:174
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:794
+msgid "City"
+msgstr "Ville"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:163
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:175
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:230
+#: lib/LATMOS/Accounts/Bases/Sql/Service.pm:70
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1285
+msgid "Comment"
+msgstr "Commentaire"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:307
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:164
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1279
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:864
+msgid "Company"
+msgstr "SociÃ©tÃ©"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:71
+msgid "Contract end"
+msgstr "Fin du contrat"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:65
+msgid "Contract start"
+msgstr "DÃ©but du contrat"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:152
+msgid "Country"
+msgstr "Pays"
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:124
+msgid "Created"
+msgstr "CrÃ©Ã©"
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:162
+msgid "Created by"
+msgstr "CrÃ©Ã© par"
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:215
+msgid "Deletion protection"
+msgstr "Protection contre l'effacement"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:161
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:744
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:869
+msgid "Department"
+msgstr "DÃ©partment"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1182
+msgid "Departments"
+msgstr "DÃ©partements"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Service.pm:62
+msgid "Depend on"
+msgstr "DÃ©pend de"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:328
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:59
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:172
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:233
+#: lib/LATMOS/Accounts/Bases/Sql/Service.pm:73
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1276
+msgid "Description"
+msgstr "Description"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:153
+msgid "Direct manager"
+msgstr "Responsable direct"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:82
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1248
+msgid "Email"
+msgstr "Email"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:167
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1282
+msgid "Employer"
+msgstr "Employeur"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:239
+msgid "Encrypted key"
+msgstr "Clef de chiffrement"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Service.pm:42
+msgid "End"
+msgstr "Fin"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:344
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:349
+msgid "End of any employment"
+msgstr "Fin de tout emplois"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:243
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:248
+msgid "End of employment"
+msgstr "Fin d'emploi"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:231
+msgid "End of entrance"
+msgstr "Fin de circuit"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:172
+msgid "End of entrance circuit"
+msgstr "Fin de circuit d'entrÃ©e"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:42
+msgid "End of warranty"
+msgstr "Fin de garantie"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:333
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:160
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:169
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:38
+msgid "Expire"
+msgstr "Expiration"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:225
+msgid "Expire on"
+msgstr "Expire le"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1187
+msgid "Expired"
+msgstr "Expiration"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1251
+msgid "External mail"
+msgstr "Email exterieur"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:285
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1096
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:859
+msgid "Fax number"
+msgstr "Fax"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:130
+msgid "Final recipient"
+msgstr "Destinataire final"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:94
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1272
+msgid "First name"
+msgstr "PrÃ©nom"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:315
+msgid "For contact only"
+msgstr "Pour contact uniquement"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:69
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:616
+msgid "Forward"
+msgstr "Renvois"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:183
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:163
+msgid "From group type"
+msgstr "Depuis les groupe-type:"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:932
+msgid "GECOS"
+msgstr "GECOS"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:43
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:187
+msgid "GID"
+msgstr "GID"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1268
+msgid "HAL id"
+msgstr "Identifiant HAL"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:105
+msgid "Hardware address"
+msgstr "Adresse matÃ©rielle"
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:144
+msgid "Hidden"
+msgstr "CachÃ©"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1265
+msgid "Home directory"
+msgstr "Home"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:236
+msgid "Host's type"
+msgstr "Type de matÃ©riel"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:176
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1402
+msgid "Hosted"
+msgstr "HÃ©bergÃ©"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1257
+msgid "Initials"
+msgstr "Initiales"
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:210
+msgid "Internal object"
+msgstr "Objet interne"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:248
+msgid "Inventory number"
+msgstr "NumÃ©ro d'inventaire"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:65
+msgid "Ip"
+msgstr "Ip"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:89
+msgid "Ip from..."
+msgstr "Ip depuis..."
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:166
+msgid "Label"
+msgstr "Etiquette"
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:129
+msgid "Last modified"
+msgstr "DerniÃšre modification"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:70
+msgid "Last name"
+msgstr "Nom"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:265
+msgid "Leaving"
+msgstr "DÃ©part"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:113
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:86
+msgid "Length"
+msgstr "DurÃ©e"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:219
+msgid "Locked"
+msgstr "VÃ©rouillÃ©"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:900
+msgid "Login"
+msgstr "Login"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:311
+msgid "Main address"
+msgstr "Adresse principale"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:119
+#: lib/LATMOS/Accounts/Bases/Sql/Service.pm:67
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1113
+msgid "Manager"
+msgstr "Responsable"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:298
+msgid "Maximal last day"
+msgstr "Dernier jour possible"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:106
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:82
+msgid "Member"
+msgstr "Membre"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:184
+msgid "Member count"
+msgstr "Nb de membres"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:567
+msgid "Member of"
+msgstr "Membre de"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:257
+msgid "Minimal first day"
+msgstr "Premier jour possible"
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:156
+msgid "Modified by"
+msgstr "ModifiÃ© par"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:130
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1234
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:947
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:119
+msgid "Name"
+msgstr "Nom"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1231
+msgid "Native first name"
+msgstr "PrÃ©nom"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1228
+msgid "Native name"
+msgstr "Nom"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:158
+msgid "NetZones"
+msgstr "NetZones"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:219
+msgid "Next"
+msgstr "Suivant"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1244
+msgid "Nickname"
+msgstr "Surnom"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:191
+msgid "No dynamic IP"
+msgstr "Pas d'IP dynamique"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:196
+msgid "No puppet"
+msgstr "Pas de classe puppet"
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:167
+msgid "Object alias for"
+msgstr "Alias d'objet pour"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:322
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:895
+msgid "Office"
+msgstr "Bureau"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:201
+msgid "Other name"
+msgstr "Autre Nom (A)"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:122
+msgid "Owner"
+msgstr "PropriÃ©taire"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:135
+msgid "Parents"
+msgstr "Parents"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1290
+msgid "Password set"
+msgstr "Mot de passe mis"
+
+#. ($attribute)
+#: lib/LATMOS/Accounts/Bases/Objects.pm:672
+#: lib/LATMOS/Accounts/Bases/Objects.pm:709
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:854
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:919
+msgid "Permission denied to read attribute %s"
+msgstr "Permission non accordÃ© pour lire l'attribut %s"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:319
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:882
+msgid "Phone number"
+msgstr "TÃ©lÃ©phone"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:241
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:834
+msgid "Post office box"
+msgstr "Boite postale"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1092
+msgid "Postal Address"
+msgstr "Adresse postale"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:117
+msgid "Postal address"
+msgstr "Adresse postale"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:196
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:807
+msgid "Postal code"
+msgstr "Code postal"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:199
+msgid "Previous"
+msgstr "PrÃ©cÃ©dent"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:162
+msgid "Puppet Class"
+msgstr "Classe puppet"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:116
+msgid "Related"
+msgstr "Voir aussi"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:179
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1408
+msgid "Request id"
+msgstr "Demande nÂ°"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:736
+msgid "Responsible"
+msgstr "Responsable"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:242
+msgid "Reverse name"
+msgstr "Nom inverse"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:205
+msgid "SSH finger print"
+msgstr "Empreinte SSH"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:136
+msgid "Secondary Manager"
+msgstr "Gestionnaires secondaires"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:245
+msgid "Serial number"
+msgstr "NumÃ©ro de sÃ©rie"
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:150
+msgid "Service"
+msgstr "Service"
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:242
+msgid "Set me as reference"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:191
+msgid "Shell"
+msgstr "Shell"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:58
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:252
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:769
+msgid "Site"
+msgstr "Site"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Service.pm:38
+msgid "Start"
+msgstr "DÃ©but"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:454
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:459
+msgid "Start of any employment"
+msgstr "Debut de tout emplois"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:260
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:361
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:366
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:378
+msgid "Start of employment"
+msgstr "Debut d'emploi"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:134
+msgid "State"
+msgstr "Ãtat"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1195
+msgid "Statut du compte"
+msgstr "Statut du compte"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:219
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:263
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:821
+msgid "Street"
+msgstr "Rue"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:141
+msgid "Structure"
+msgstr "Structure"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:142
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:752
+msgid "Type of contract"
+msgstr "Type de contrat"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:142
+msgid "UID"
+msgstr "UID"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:54
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:155
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:56
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:127
+msgid "User"
+msgstr "Utilisateur"
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:188
+msgid "User excluded"
+msgstr "Utilisateurs exclus"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1223
+msgid "Web Page"
+msgstr "Page Web"
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:474
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:499
+msgid "Work duration"
+msgstr "DurÃ©e de prÃ©sence"
+
+#~ msgid "Contact"
+#~ msgstr "Contact"
+
+#~ msgid "First day"
+#~ msgstr "Premier jour"
+
+#~ msgid "Last day"
+#~ msgstr "Dernier jour"
+
+#~ msgid "o"
+#~ msgstr "SociÃ©tÃ©"
Index: tags/5.2.23/LATMOS-Accounts/po/messages.pot
===================================================================
--- tags/5.2.23/LATMOS-Accounts/po/messages.pot	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/po/messages.pot	(revision 2119)
@@ -0,0 +1,498 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: 1\n"
+"POT-Creation-Date: 2015-12-28 17:00+01\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Olivier Thauvin <olivier.thauvin@latmos.ipsl.fr\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. ($Dd)
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:110 lib/LATMOS/Accounts/Bases/Sql/User.pm:496
+msgid "%d days"
+msgstr ""
+
+#. ($Dm)
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:109 lib/LATMOS/Accounts/Bases/Sql/User.pm:495
+msgid "%d months"
+msgstr ""
+
+#. ($Dy)
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:108 lib/LATMOS/Accounts/Bases/Sql/User.pm:494
+msgid "%d years"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1405
+msgid "Account Request id"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1191
+msgid "Active"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:111 lib/LATMOS/Accounts/Bases/Sql/User.pm:670
+msgid "Aliases"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:383
+msgid "Arrival"
+msgstr ""
+
+#. ($attribute)
+#: lib/LATMOS/Accounts/Bases/Objects.pm:668 lib/LATMOS/Accounts/Bases/Objects.pm:705 lib/LATMOS/Accounts/Bases/Sql/objects.pm:850 lib/LATMOS/Accounts/Bases/Sql/objects.pm:915
+msgid "Attribute %s is not readable"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:173 lib/LATMOS/Accounts/Bases/Sql/Group.pm:152
+msgid "Automatics filters"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:205
+msgid "Cache for oalias data"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases.pm:483
+msgid "Cannot create %s object with empty id"
+msgstr ""
+
+#. ($base)
+#: bin/la-load-csv:59 bin/la-query:117 bin/la-renewEmp:62
+msgid "Cannot load base %s\n"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:174 lib/LATMOS/Accounts/Bases/Sql/User.pm:794
+msgid "City"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:163 lib/LATMOS/Accounts/Bases/Sql/Group.pm:175 lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:230 lib/LATMOS/Accounts/Bases/Sql/Service.pm:70 lib/LATMOS/Accounts/Bases/Sql/User.pm:1285
+msgid "Comment"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:307 lib/LATMOS/Accounts/Bases/Sql/Employment.pm:164 lib/LATMOS/Accounts/Bases/Sql/User.pm:1279 lib/LATMOS/Accounts/Bases/Sql/User.pm:864
+msgid "Company"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:71
+msgid "Contract end"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:65
+msgid "Contract start"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:152
+msgid "Country"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:124
+msgid "Created"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:162
+msgid "Created by"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:215
+msgid "Deletion protection"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:161 lib/LATMOS/Accounts/Bases/Sql/User.pm:744 lib/LATMOS/Accounts/Bases/Sql/User.pm:869
+msgid "Department"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1182
+msgid "Departments"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Service.pm:62
+msgid "Depend on"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:328 lib/LATMOS/Accounts/Bases/Sql/Employment.pm:59 lib/LATMOS/Accounts/Bases/Sql/Group.pm:172 lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:233 lib/LATMOS/Accounts/Bases/Sql/Service.pm:73 lib/LATMOS/Accounts/Bases/Sql/User.pm:1276
+msgid "Description"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:153
+msgid "Direct manager"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:82 lib/LATMOS/Accounts/Bases/Sql/User.pm:1248
+msgid "Email"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:167 lib/LATMOS/Accounts/Bases/Sql/User.pm:1282
+msgid "Employer"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:239
+msgid "Encrypted key"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Service.pm:42
+msgid "End"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:344 lib/LATMOS/Accounts/Bases/Sql/User.pm:349
+msgid "End of any employment"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:243 lib/LATMOS/Accounts/Bases/Sql/User.pm:248
+msgid "End of employment"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:231
+msgid "End of entrance"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:172
+msgid "End of entrance circuit"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:42
+msgid "End of warranty"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:333 lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:160 lib/LATMOS/Accounts/Bases/Sql/Group.pm:169 lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:38
+msgid "Expire"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:225
+msgid "Expire on"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1187
+msgid "Expired"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1251
+msgid "External mail"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:285 lib/LATMOS/Accounts/Bases/Sql/User.pm:1096 lib/LATMOS/Accounts/Bases/Sql/User.pm:859
+msgid "Fax number"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:130
+msgid "Final recipient"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:94 lib/LATMOS/Accounts/Bases/Sql/User.pm:1272
+msgid "First name"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:315
+msgid "For contact only"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:69 lib/LATMOS/Accounts/Bases/Sql/User.pm:616
+msgid "Forward"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:183 lib/LATMOS/Accounts/Bases/Sql/Group.pm:163
+msgid "From group type"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:932
+msgid "GECOS"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:43 lib/LATMOS/Accounts/Bases/Sql/User.pm:187
+msgid "GID"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1268
+msgid "HAL id"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:105
+msgid "Hardware address"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:144
+msgid "Hidden"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1265
+msgid "Home directory"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:236
+msgid "Host's type"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:176 lib/LATMOS/Accounts/Bases/Sql/User.pm:1402
+msgid "Hosted"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1257
+msgid "Initials"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:210
+msgid "Internal object"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:248
+msgid "Inventory number"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:65
+msgid "Ip"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:89
+msgid "Ip from..."
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:166
+msgid "Label"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:129
+msgid "Last modified"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:70
+msgid "Last name"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:265
+msgid "Leaving"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:113 lib/LATMOS/Accounts/Bases/Sql/Employment.pm:86
+msgid "Length"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:219
+msgid "Locked"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:900
+msgid "Login"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:311
+msgid "Main address"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:119 lib/LATMOS/Accounts/Bases/Sql/Service.pm:67 lib/LATMOS/Accounts/Bases/Sql/User.pm:1113
+msgid "Manager"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:298
+msgid "Maximal last day"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:106 lib/LATMOS/Accounts/Bases/Sql/Group.pm:82
+msgid "Member"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:184
+msgid "Member count"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:567
+msgid "Member of"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:257
+msgid "Minimal first day"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:156
+msgid "Modified by"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:130 lib/LATMOS/Accounts/Bases/Sql/User.pm:1234 lib/LATMOS/Accounts/Bases/Sql/User.pm:947 lib/LATMOS/Accounts/Bases/Sql/objects.pm:119
+msgid "Name"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1231
+msgid "Native first name"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1228
+msgid "Native name"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:158
+msgid "NetZones"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:219
+msgid "Next"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1244
+msgid "Nickname"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:191
+msgid "No dynamic IP"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:196
+msgid "No puppet"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:167
+msgid "Object alias for"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:322 lib/LATMOS/Accounts/Bases/Sql/User.pm:895
+msgid "Office"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:201
+msgid "Other name"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:122
+msgid "Owner"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:135
+msgid "Parents"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1290
+msgid "Password set"
+msgstr ""
+
+#. ($attribute)
+#: lib/LATMOS/Accounts/Bases/Objects.pm:672 lib/LATMOS/Accounts/Bases/Objects.pm:709 lib/LATMOS/Accounts/Bases/Sql/objects.pm:854 lib/LATMOS/Accounts/Bases/Sql/objects.pm:919
+msgid "Permission denied to read attribute %s"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:319 lib/LATMOS/Accounts/Bases/Sql/User.pm:882
+msgid "Phone number"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:241 lib/LATMOS/Accounts/Bases/Sql/User.pm:834
+msgid "Post office box"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1092
+msgid "Postal Address"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:117
+msgid "Postal address"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:196 lib/LATMOS/Accounts/Bases/Sql/User.pm:807
+msgid "Postal code"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:199
+msgid "Previous"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:162
+msgid "Puppet Class"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:116
+msgid "Related"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:179 lib/LATMOS/Accounts/Bases/Sql/User.pm:1408
+msgid "Request id"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:736
+msgid "Responsible"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:242
+msgid "Reverse name"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:205
+msgid "SSH finger print"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:136
+msgid "Secondary Manager"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:245
+msgid "Serial number"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:150
+msgid "Service"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/objects.pm:242
+msgid "Set me as reference"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:191
+msgid "Shell"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:58 lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:252 lib/LATMOS/Accounts/Bases/Sql/User.pm:769
+msgid "Site"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Service.pm:38
+msgid "Start"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:454 lib/LATMOS/Accounts/Bases/Sql/User.pm:459
+msgid "Start of any employment"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:260 lib/LATMOS/Accounts/Bases/Sql/User.pm:361 lib/LATMOS/Accounts/Bases/Sql/User.pm:366 lib/LATMOS/Accounts/Bases/Sql/User.pm:378
+msgid "Start of employment"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:134
+msgid "State"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1195
+msgid "Statut du compte"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:219 lib/LATMOS/Accounts/Bases/Sql/Address.pm:263 lib/LATMOS/Accounts/Bases/Sql/User.pm:821
+msgid "Street"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Group.pm:141
+msgid "Structure"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Employment.pm:142 lib/LATMOS/Accounts/Bases/Sql/User.pm:752
+msgid "Type of contract"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:142
+msgid "UID"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Address.pm:54 lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:155 lib/LATMOS/Accounts/Bases/Sql/Employment.pm:56 lib/LATMOS/Accounts/Bases/Sql/Nethost.pm:127
+msgid "User"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/Aliases.pm:188
+msgid "User excluded"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:1223
+msgid "Web Page"
+msgstr ""
+
+#: lib/LATMOS/Accounts/Bases/Sql/User.pm:474 lib/LATMOS/Accounts/Bases/Sql/User.pm:499
+msgid "Work duration"
+msgstr ""
Index: tags/5.2.23/LATMOS-Accounts/sample/allowed_values.ini
===================================================================
--- tags/5.2.23/LATMOS-Accounts/sample/allowed_values.ini	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/sample/allowed_values.ini	(revision 2119)
@@ -0,0 +1,7 @@
+## $Id$
+
+## OBJECT.ATTRIBUTE
+#[user.l]
+## allowed = authorized value, can be set multiple time
+# allowed = London
+# allowed = Paris
Index: tags/5.2.23/LATMOS-Accounts/sample/la-allowed-values.ini
===================================================================
--- tags/5.2.23/LATMOS-Accounts/sample/la-allowed-values.ini	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/sample/la-allowed-values.ini	(revision 2119)
@@ -0,0 +1,10 @@
+## $Id: la-allowed-values.ini 3157 2010-08-29 18:11:12Z nanardon $
+
+# Empty section to make the file valid
+[_]
+
+## OBJECT.ATTRIBUTE
+#[user.l]
+## allowed = authorized value, can be set multiple time
+# allowed = London
+# allowed = Paris
Index: tags/5.2.23/LATMOS-Accounts/sample/la-sync-list.ini
===================================================================
--- tags/5.2.23/LATMOS-Accounts/sample/la-sync-list.ini	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/sample/la-sync-list.ini	(revision 2119)
@@ -0,0 +1,157 @@
+# $Id: la-sync-list.ini 3152 2010-08-29 15:11:18Z nanardon $
+
+# default values
+[_default_]
+# Command to run (%% is replace by section name)
+# example:
+# cmd=ssh root@python.latmos.ipsl.fr sync_members %%
+# This command jump dump data :)
+cmd=cat > /tmp/listes/%%
+# object type we want to list, users, what else ?
+object=user
+# formatted line
+# fmt = %{mail}
+# use addtolist to add an entry all the time
+# addtolist = test@test.com
+# a command to run before processing
+# pre =
+# a command to run after processing
+# post =
+# after each file:
+# post_file =
+
+# Each section may override default
+# [listname]
+# filter=...
+# filter=...
+
+[tous]
+# people in LATMOS having mail:
+filter=company=LATMOS
+filter=mail=*
+filter2=company~Novimet
+filter2=contratType=heberges
+filter2=mail=*
+
+[sitevlb]
+filter=allsite=VerriÃšres
+filter=mail=*
+
+[sitevel]
+filter=allsite=VÃ©lizy
+filter=mail=*
+
+[sitejus]
+filter=allsite=Jussieu
+filter=mail=*
+
+# Departement
+
+[impec]                                                                                                                     
+filter=memberOf=impec
+filter=mail=*
+
+[heppi]
+filter=memberOf=heppi
+filter=mail=*
+
+[ester]
+filter=memberOf=ester
+filter=mail=*
+
+[tilde]
+filter=memberOf=tilde
+filter=mail=*
+
+[admg]
+filter=memberOf=admg
+filter=mail=*
+
+[tact]
+filter=memberOf=tact
+filter=mail=*
+
+[space]
+filter=memberOf=space
+filter=mail=*
+
+[shti]
+filter=memberOf=shti
+filter=mail=*
+
+[manip]
+filter=memberOf=manip
+filter=mail=*
+
+# Cellules
+[oric]
+filter=memberOf=oric
+filter=mail=*
+
+[ismad]
+filter=memberOf=ismad
+filter=mail=*
+
+[celi3]
+filter=memberOf=celi3
+filter=mail=*
+
+[celec]
+filter=memberOf=celec
+filter=mail=*
+
+[adop]
+filter=memberOf=adop
+filter=mail=*
+
+[atom]
+filter=memberOf=atom
+filter=mail=*
+
+[cdds]
+filter=company=LATMOS
+filter=contratType=CDD
+filter=mail=*
+
+# Liste selon catÃ©gorie de personnel:
+
+[cher]
+filter=jobType=Chercheur
+filter=jobType=Enseignant chercheur
+filter=mail=*
+
+[cnap]
+filter=jobType=CNAP
+filter=mail=*
+
+[cnrscher]
+filter=jobType=Chercheur
+filter=mail=*
+
+[cnrsita]
+filter=jobType=ITA
+filter=mail=*
+
+[univens]
+filter=jobType=Enseignant chercheur
+filter=mail=*
+
+[univita]
+filter=jobType=ITARF
+filter=mail=*
+
+[itas]
+company=LATMOS
+filter=jobType=ITA
+filter=jobType=ITARF
+filter=mail=*
+
+[stage]
+filter=contratType=Stage
+filter=mail=*
+
+[these]
+filter=contratType=ThÃšse
+filter=mail=*
+
+
Index: tags/5.2.23/LATMOS-Accounts/sample/la-sync-manager.ini
===================================================================
--- tags/5.2.23/LATMOS-Accounts/sample/la-sync-manager.ini	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/sample/la-sync-manager.ini	(revision 2119)
@@ -0,0 +1,6 @@
+[dummy]
+lazy=1
+
+[bases]
+type=basessynchro
+
Index: tags/5.2.23/LATMOS-Accounts/sample/latmos-accounts.ini
===================================================================
--- tags/5.2.23/LATMOS-Accounts/sample/latmos-accounts.ini	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/sample/latmos-accounts.ini	(revision 2119)
@@ -0,0 +1,50 @@
+# $Id: latmos-accounts.ini 3150 2010-08-29 14:47:00Z nanardon $
+
+# section for default values:
+[_default_]
+# The master base, if unset the first one
+base = latmosc
+# Default synchro to apply
+sync = name
+# smtp: the server used to send mail
+smtp = localhost
+# the From mail header for mail
+mailFrom = svp@localhost
+# An address always receiving mails as cc
+allwayscc = 
+# The base templates directory
+templatespath = /usr/share/latmos-accounts/templates
+# The directory where status get writen, directory must exists
+state_dir =
+
+
+[_defattr_]
+# default attribute/value to set
+# OBJECT.ATTR = value
+# user.min_uid is min uid for automatic assigment
+# user.max_uid is max uid for automatic assigment
+# group.min_gid is min gid for automatic assigment
+# group.max_gid is max gid for automatic assigment
+# user.homebase directory where Home will be created
+
+# An accounts base:
+[base1]
+# Type of driver
+type=sql
+# data from this base are push to:
+push=unix
+# all other param are passed to driver
+db_conn=host=localhost;dbname=base1
+# A command to run after commit
+# postcommit = 
+
+[unix]
+type=unix
+
+# A synchronisation definition
+[sync:name]
+# source
+from = base1
+# destination, can be multiple
+to = unix
+#to = others
Index: tags/5.2.23/LATMOS-Accounts/sample/list.cfg
===================================================================
--- tags/5.2.23/LATMOS-Accounts/sample/list.cfg	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/sample/list.cfg	(revision 2119)
@@ -0,0 +1,151 @@
+# $Id$
+
+# default values
+[_default_]
+# Command to run (%% is replace by section name)
+# example:
+# cmd=ssh root@python.latmos.ipsl.fr sync_members %%
+# This command jump dump data :)
+cmd=cat > /tmp/listes/%%
+# object type we want to list, users, what else ?
+object=user
+# formatted line
+# fmt = %{mail}
+# use addtolist to add an entry all the time
+# addtolist = test@test.com
+
+# Each section may override default
+# [listname]
+# filter=...
+# filter=...
+
+[tous]
+# people in LATMOS having mail:
+filter=company=LATMOS
+filter=mail=*
+filter2=company~Novimet
+filter2=contratType=heberges
+filter2=mail=*
+
+[sitevlb]
+filter=allsite=VerriÃšres
+filter=mail=*
+
+[sitevel]
+filter=allsite=VÃ©lizy
+filter=mail=*
+
+[sitejus]
+filter=allsite=Jussieu
+filter=mail=*
+
+# Departement
+
+[impec]                                                                                                                     
+filter=memberOf=impec
+filter=mail=*
+
+[heppi]
+filter=memberOf=heppi
+filter=mail=*
+
+[ester]
+filter=memberOf=ester
+filter=mail=*
+
+[tilde]
+filter=memberOf=tilde
+filter=mail=*
+
+[admg]
+filter=memberOf=admg
+filter=mail=*
+
+[tact]
+filter=memberOf=tact
+filter=mail=*
+
+[space]
+filter=memberOf=space
+filter=mail=*
+
+[shti]
+filter=memberOf=shti
+filter=mail=*
+
+[manip]
+filter=memberOf=manip
+filter=mail=*
+
+# Cellules
+[oric]
+filter=memberOf=oric
+filter=mail=*
+
+[ismad]
+filter=memberOf=ismad
+filter=mail=*
+
+[celi3]
+filter=memberOf=celi3
+filter=mail=*
+
+[celec]
+filter=memberOf=celec
+filter=mail=*
+
+[adop]
+filter=memberOf=adop
+filter=mail=*
+
+[atom]
+filter=memberOf=atom
+filter=mail=*
+
+[cdds]
+filter=company=LATMOS
+filter=contratType=CDD
+filter=mail=*
+
+# Liste selon catÃ©gorie de personnel:
+
+[cher]
+filter=jobType=Chercheur
+filter=jobType=Enseignant chercheur
+filter=mail=*
+
+[cnap]
+filter=jobType=CNAP
+filter=mail=*
+
+[cnrscher]
+filter=jobType=Chercheur
+filter=mail=*
+
+[cnrsita]
+filter=jobType=ITA
+filter=mail=*
+
+[univens]
+filter=jobType=Enseignant chercheur
+filter=mail=*
+
+[univita]
+filter=jobType=ITARF
+filter=mail=*
+
+[itas]
+company=LATMOS
+filter=jobType=ITA
+filter=jobType=ITARF
+filter=mail=*
+
+[stage]
+filter=contratType=Stage
+filter=mail=*
+
+[these]
+filter=contratType=ThÃšse
+filter=mail=*
+
+
Index: tags/5.2.23/LATMOS-Accounts/scripts/ochelper
===================================================================
--- tags/5.2.23/LATMOS-Accounts/scripts/ochelper	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/scripts/ochelper	(revision 2119)
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use LATMOS::Accounts;
+use LATMOS::Accounts::Bases::OCHelper;
+use Getopt::Long;
+use Pod::Usage;
+
+# $Id: ochelper 2038 2010-04-17 14:39:41Z nanardon $
+
+GetOptions(
+    'c|config=s'        => \my $config,
+    'b|base=s'          => \my $base,
+    'o|object=s'        => \my $otype,
+    'e|empty'           => \my $empty_attr,
+    'fmt=s'             => \my $fmt,
+    'filefmt=s'         => \my $filefmt,
+    'ro'                => \my $with_ro,
+    'no-unexp|wo-unexp' => \my $nounexp,
+    'with-unexp'        => \my $unexp,
+    'help'              => sub { pod2usage(0) },
+) or pod2usage();
+
+$otype ||= 'user';
+
+my $LA = LATMOS::Accounts->new($config, noacl => 1);
+my $labase = $base ? $LA->base($base) : $LA->default_base;
+$labase && $labase->load or die "Cannot load base";
+
+$labase->unexported($nounexp ? 0 : 1);
+
+my $helper = $labase->ochelper($otype);
+
+my $info = undef;
+while (1) {
+    my $status;
+    ($status, $info) = $helper->step($info);
+    
+    if ($status ne 'NEEDINFO') {
+        if ($status eq 'CREATED') {
+            print "Object created\n";
+        } else {
+            print "Nothing done\n";     
+        }
+        last;
+    }
+
+    if ($info->{name}{ask}) {
+        print "NAME:\n";
+        my $line = <STDIN>;
+        chomp($line);
+        $info->{name}{content} = $line;
+    }
+    foreach (@{$info->{ask} || []}) {
+        print "$_ " . ($info->{contents}{$_} ? '(' . $info->{contents}{$_} . ')' :
+            '-') . "\n";
+        my $line = <STDIN>;
+        chomp($line);
+        $info->{contents}{$_} = $line if($line);
+    }
+}
+
Index: tags/5.2.23/LATMOS-Accounts/scripts/pod2xhtml.pl
===================================================================
--- tags/5.2.23/LATMOS-Accounts/scripts/pod2xhtml.pl	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/scripts/pod2xhtml.pl	(revision 2119)
@@ -0,0 +1,39 @@
+#!/bin/env perl
+
+use strict;
+use warnings;
+use Pod::Xhtml;
+use Pod::Hyperlink::BounceURL;
+use Pod::Checker;
+
+my $linkparser = new Pod::Hyperlink::BounceURL;
+$linkparser->configure( URL => './%s.html' );
+
+my $parser = new Pod::Xhtml(
+    LinkParser => $linkparser,
+    MakeIndex => 1,
+);
+
+my $dest = shift(@ARGV);
+
+if (! -d "html/$dest") {
+   mkdir "html/$dest" || die "Cannot create html/$dest: $!";
+}
+
+foreach my $f (@ARGV) {
+    next if (podchecker($f));
+    my $file = $f;
+    if ($file =~ m/\.pm$/) {
+        $file =~ s:.*lib/::;
+        $file =~ s;/+;::;g;
+    } else {
+        $file =~ s:^.*/::;
+    }
+    $file =~ s:(\.[^\.]+)?$:.html:;
+    $parser->parse_from_file( 
+       $f,
+       "html/$dest/$file",
+    );
+}
+
+
Index: tags/5.2.23/LATMOS-Accounts/sqldata/base.sql
===================================================================
--- tags/5.2.23/LATMOS-Accounts/sqldata/base.sql	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/sqldata/base.sql	(revision 2119)
@@ -0,0 +1,3084 @@
+--
+-- PostgreSQL database dump
+--
+
+SET statement_timeout = 0;
+SET client_encoding = 'UTF8';
+SET standard_conforming_strings = off;
+SET check_function_bodies = false;
+SET client_min_messages = warning;
+SET escape_string_warning = off;
+
+--
+-- Name: plpgsql; Type: PROCEDURAL LANGUAGE; Schema: -; Owner: -
+--
+
+CREATE PROCEDURAL LANGUAGE plpgsql;
+
+
+SET search_path = public, pg_catalog;
+
+--
+-- Name: accreq_attr_update_ref(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION accreq_attr_update_ref() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+IF (TG_OP != 'INSERT') then
+update "accreq" set date = now() where "accreq".ikey = old.okey;
+end if;
+IF (TG_OP != 'DELETE') then
+update "accreq"  set date = now() where "accreq".ikey = new.okey;
+end if;
+
+IF (TG_OP = 'DELETE') then
+  return old;
+ELSE
+  return new;
+END IF;
+
+END;$$;
+
+
+--
+-- Name: address_attr_update_ref(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION address_attr_update_ref() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+IF (TG_OP != 'INSERT') then
+update "address" set date = now() where "address".ikey = old.okey;
+end if;
+IF (TG_OP != 'DELETE') then
+update "address"  set date = now() where "address".ikey = new.okey;
+end if;
+
+IF (TG_OP = 'DELETE') then
+  return old;
+ELSE
+  return new;
+END IF;
+
+END;$$;
+
+
+--
+-- Name: address_sort_fields(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION address_sort_fields() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$BEGIN
+
+IF (TG_OP='INSERT') then
+IF (new.attr='site') THEN
+insert into address_attributes_site VALUES (new.*);
+RETURN NULL;
+END IF;
+
+IF (new.attr = 'isMainAddress') then
+-- remove isMainAddress from other address
+-- for of the user
+delete from address_attributes_base where
+attr = 'isMainAddress' and
+okey in (
+select addr.ikey from address as addr join
+  address on addr.user = address.user
+  where 
+  address.ikey = new.okey and addr.ikey != new.okey
+);
+END IF;
+end if;
+
+if (TG_OP='DELETE') THEN
+RETURN old;
+else
+RETURN new;
+end if;
+END;$$;
+
+
+--
+-- Name: address_update_user_rev(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION address_update_user_rev() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+IF (TG_OP = 'DELETE') THEN
+update "user" set "date" = now() where "name" = old."user";
+return old;
+ELSE
+update "user" set "date" = now() where "name" = new."user";
+return new;
+end if;
+
+end;$$;
+
+
+--
+-- Name: aliases_nickname(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION aliases_nickname() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+IF TG_OP='DELETE' THEN
+delete from user_attributes where attr='nickname' and value=old.name;
+RETURN old;
+END IF;
+
+IF TG_OP='UPDATE' THEN
+IF new.forward <> old.forward THEN
+delete from user_attributes where attr='nickname' and value=old.name;
+RETURN new;
+END IF;
+END IF;
+
+IF TG_OP='DELETE' then
+return old;
+ELSE
+return new;
+END IF;
+END;$$;
+
+--
+-- Name: department_group_upd_f(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION department_group_upd_f() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$BEGIN
+
+IF (new.attr = 'department') then
+  BEGIN
+  insert into group_attributes (okey, attr, value)
+  select "group"."ikey", 'memberUID', "user"."name"
+  from "user", "group"
+  where "group"."name" = new."value" and "user".ikey = new.okey;
+  EXCEPTION WHEN unique_violation THEN
+  END;
+END IF;
+
+RETURN new;
+END;$$;
+
+
+--
+-- Name: fmt_macaddr(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION fmt_macaddr() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+new.value := new.value::macaddr::text;
+
+return new;
+end;$$;
+
+
+--
+-- Name: group_attr_update_ref(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION group_attr_update_ref() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+IF (TG_OP != 'INSERT') then
+update "group"  set date = now() where "group"."ikey" = old.okey;
+end if;
+IF (TG_OP != 'DELETE') then
+update "group"  set date = now() where "group"."ikey" = new.okey;
+end if;
+
+IF (TG_OP = 'DELETE') then
+  return old;
+ELSE
+  return new;
+END IF;
+
+END;$$;
+
+
+--
+-- Name: group_attr_user_update_user_ref(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION group_attr_user_update_user_ref() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+IF (TG_OP != 'INSERT') then
+update "user"  set date = now() where "user"."name" = old.value;
+end if;
+IF (TG_OP != 'DELETE') then
+update "user"  set date = now() where "user"."name" = new.value;
+end if;
+
+IF (TG_OP = 'DELETE') then
+  return old;
+ELSE
+  return new;
+END IF;
+
+END;$$;
+
+
+--
+-- Name: group_sort_fields(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION group_sort_fields() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$BEGIN
+
+IF (TG_OP='INSERT') then
+IF (new.attr='managedBy'
+OR  new.attr='member'
+OR  new.attr='memberUID') THEN
+insert into group_attributes_users VALUES (new.*);
+RETURN NULL;
+END IF;
+
+IF (new.attr='sutype') THEN
+insert into group_attributes_sutypes VALUES (new.*);
+RETURN NULL;
+END IF;
+end if;
+
+
+IF (TG_OP = 'UPDATE') THEN
+IF (new.attr='gidNumber') then
+update "group" set gidnumber = new.value::integer where "group".ikey = new.okey;
+RETURN NULL;
+END IF;
+END IF;
+
+IF (TG_OP = 'DELETE') THEN
+IF (old.attr='exported') then
+update "group" set exported = false where "group".ikey = old.okey;
+return null;
+end if;
+else
+IF (new.attr='exported') then
+update "group" set exported = true where "group".ikey = new.okey;
+RETURN NULL;
+end if;
+END IF;
+
+if (TG_OP='DELETE') THEN
+RETURN old;
+else
+RETURN new;
+end if;
+END;$$;
+
+
+--
+-- Name: nethost_attr_update_ref(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION nethost_attr_update_ref() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+IF (TG_OP != 'INSERT') then
+update "nethost" set date = now() where "nethost".ikey = old.okey;
+end if;
+IF (TG_OP != 'DELETE') then
+update "nethost"  set date = now() where "nethost".ikey = new.okey;
+end if;
+
+IF (TG_OP = 'DELETE') then
+  return old;
+ELSE
+  return new;
+END IF;
+
+END;$$;
+
+
+--
+-- Name: nethost_sort_fields(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION nethost_sort_fields() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$BEGIN
+
+IF (TG_OP='INSERT') then
+IF (new.attr='ip') THEN
+insert into nethost_attributes_ips VALUES (new.*);
+RETURN NULL;
+END IF;
+
+IF (new.attr='macaddr') THEN
+insert into nethost_attributes_macs VALUES (new.*);
+RETURN NULL;
+END IF;
+
+IF (new.attr='owner') THEN
+insert into nethost_attributes_users VALUES (new.*);
+RETURN NULL;
+END IF;
+end if;
+
+
+
+if (TG_OP='DELETE') THEN
+RETURN old;
+else
+RETURN new;
+end if;
+END;$$;
+
+
+--
+-- Name: nethost_upd_zone(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION nethost_upd_zone() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+  IF (TG_OP = 'DELETE') THEN
+    update netzone set date=now() where ikey in
+    (
+    select okey from netzone_attributes where attr='allow_dyn'
+    union
+    select netzone_attributes.okey from netzone_attributes join
+    nethost_attributes_ips on netzone_attributes.attr='net' and 
+    nethost_attributes_ips.value::inet <<= netzone_attributes.value::inet
+    where nethost_attributes_ips.okey = old.ikey
+    );
+  END IF;
+  IF (TG_OP = 'INSERT' or TG_OP = 'UPDATE') THEN
+    update netzone set date=now() where ikey in
+    (
+    select okey from netzone_attributes where attr='allow_dyn'
+    union
+    select netzone_attributes.okey from netzone_attributes join
+    nethost_attributes_ips on netzone_attributes.attr='net' and 
+    nethost_attributes_ips.value::inet <<= netzone_attributes.value::inet
+    where nethost_attributes_ips.okey = new.ikey
+    );
+  END IF;
+
+ IF (TG_OP = 'DELETE') THEN
+   return old;
+ ELSE
+   return new;
+ END IF;
+
+END;$$;
+
+
+--
+-- Name: nethostipdel_upd_zone(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION nethostipdel_upd_zone() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+
+    update netzone set date=now() where ikey in
+    (
+    select okey from netzone_attributes where attr='allow_dyn'
+    union
+    select okey from netzone_attributes where attr='net' and
+    old.value::inet <<= netzone_attributes.value::inet
+    );
+
+   return old;
+ 
+END;$$;
+
+
+--
+-- Name: netzone_attr_update_ref(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION netzone_attr_update_ref() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+IF (TG_OP != 'INSERT') then
+update "netzone" set date = now() where "netzone".ikey = old.okey;
+end if;
+IF (TG_OP != 'DELETE') then
+update "netzone"  set date = now() where "netzone".ikey = new.okey;
+end if;
+
+IF (TG_OP = 'DELETE') then
+  return old;
+ELSE
+  return new;
+END IF;
+
+END;$$;
+
+
+--
+-- Name: netzone_sort_fields(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION netzone_sort_fields() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$BEGIN
+
+IF (TG_OP='INSERT') then
+IF (new.attr='site') THEN
+insert into netzone_attributes_site VALUES (new.*);
+RETURN NULL;
+END IF;
+
+IF (new.attr='dynFrom') THEN
+insert into netzone_attributes_netzone VALUES (new.*);
+RETURN NULL;
+END IF;
+
+end if;
+
+if (TG_OP='DELETE') THEN
+RETURN old;
+else
+RETURN new;
+end if;
+END;$$;
+
+
+--
+-- Name: person_attr_update_ref(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION person_attr_update_ref() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+IF (TG_OP != 'INSERT') then
+update "user" set date = now() where "user".ikey = old.okey;
+update "group" set date = now() where "group".name = old.value;
+end if;
+IF (TG_OP != 'DELETE') then
+update "user"  set date = now() where "user".ikey = new.okey;
+update "group" set date = now() where "group".name = new.value;
+end if;
+
+IF (TG_OP = 'DELETE') then
+  return old;
+ELSE
+  return new;
+END IF;
+
+END;$$;
+
+
+--
+-- Name: rev_tg_f(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION rev_tg_f() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+IF (TG_OP = 'DELETE') THEN
+  update settings set val = old.ikey where varname = 'last-delete';
+  IF NOT FOUND THEN
+    insert into settings (varname, val) values ('last-delete', old.ikey);
+  END IF;
+  return old;
+else
+  IF (TG_OP = 'INSERT') THEN
+  new.rev := nextval('revisions_rev_seq'::regclass);
+  new.date := now();
+  ELSIF (old.rev = new.rev) THEN
+  new.rev := nextval('revisions_rev_seq'::regclass);
+  new.date := now();
+  END IF;  
+  return new;
+END IF;
+
+end;$$;
+
+
+--
+-- Name: site_attr_update_ref(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION site_attr_update_ref() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+IF (TG_OP != 'INSERT') then
+update "site" set date = now() where "site".ikey = old.okey;
+end if;
+IF (TG_OP != 'DELETE') then
+update "site"  set date = now() where "site".ikey = new.okey;
+end if;
+
+IF (TG_OP = 'DELETE') then
+  return old;
+ELSE
+  return new;
+END IF;
+
+END;$$;
+
+
+--
+-- Name: update_address_site(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION update_address_site() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$begin
+
+IF (TG_OP='DELETE') then
+update address set date = now() where ikey in
+(select okey from address_attributes where attr = 'site' and
+value = old.name);
+return old;
+ELSE
+update address set date = now() where ikey in
+(select okey from address_attributes where attr = 'site' and
+value = new.name);
+return new;
+end if;
+
+end;$$;
+
+
+--
+-- Name: user_sort_fields(); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION user_sort_fields() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$DECLARE
+  rec RECORD;
+BEGIN
+
+IF (TG_OP='UPDATE' or TG_OP='INSERT') THEN
+
+IF (new.attr='locked') THEN
+  IF (TG_OP = 'INSERT') THEN
+    new.value = now()::text;
+  ELSIF (TG_OP='UPDATE') THEN
+    new.value = old.value;
+  END IF;
+END IF;
+
+IF (new.attr='nickname') THEN
+  PERFORM 1 from "user" where "user".ikey = new.okey
+  and new.value = "user".name;
+  IF NOT FOUND THEN
+    select aliases.name into rec from aliases join "user"
+    on array["user".name] = aliases.forward
+    where "user".ikey = new.okey and aliases.name = new.value;
+    IF NOT FOUND THEN
+      insert into aliases ("name", "forward")
+      select new.value, array["user".name] from "user" where "user".ikey = new.okey;
+    END IF;
+  END IF;
+END IF;
+
+IF (new.attr='uidNumber') THEN
+update "user" set uidnumber = new.value::integer where ikey = new.okey;
+RETURN NULL;
+END IF;
+
+IF (new.attr='gidNumber') THEN
+update "user" set gidnumber = new.value::integer where ikey = new.okey;
+RETURN null;
+END IF;
+
+IF (new.attr='expire') THEN
+update "user" set expire = new.value::timestamp where ikey = new.okey;
+RETURN NULL;
+END IF;
+
+IF (new.attr='exported') THEN
+update "user" set exported = true where ikey = new.okey;
+RETURN NULL;
+END IF;
+
+END IF;
+
+IF (TG_OP = 'DELETE') THEN
+
+IF (old.attr='expire') THEN
+update "user" set expire = NULL where ikey = old.okey;
+RETURN NULL;
+END IF;
+
+IF (old.attr='exported') THEN
+update "user" set exported = false where ikey = old.okey;
+RETURN NULL;
+END IF;
+
+END IF;
+
+
+IF (TG_OP='INSERT') THEN
+IF (new.attr='manager') THEN
+insert into user_attributes_users VALUES (new.*);
+RETURN NULL;
+END IF;
+
+IF (new.attr='site') THEN
+insert into user_attributes_site VALUES (new.*);
+RETURN NULL;
+END IF;
+
+IF (new.attr='department') THEN
+insert into user_attributes_groups VALUES (new.*);
+RETURN NULL;
+END IF;
+
+IF (new.attr='contratType') THEN
+insert into user_attributes_groups VALUES (new.*);
+RETURN NULL;
+END IF;
+
+IF (new.attr='jobType') THEN
+insert into user_attributes_groups VALUES (new.*);
+RETURN NULL;
+END IF;
+
+END IF;
+
+IF (TG_OP='DELETE') then
+RETURN old;
+ELSE
+RETURN new;
+end if;
+END;$$;
+
+
+--
+-- Name: ikey_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE ikey_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+SET default_tablespace = '';
+
+SET default_with_oids = false;
+
+--
+-- Name: revisions; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE revisions (
+    rev integer NOT NULL,
+    date timestamp with time zone DEFAULT now() NOT NULL,
+    "create" timestamp with time zone DEFAULT now() NOT NULL,
+    ikey integer DEFAULT nextval('ikey_seq'::regclass) NOT NULL
+);
+
+
+--
+-- Name: revisions_rev_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE revisions_rev_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+--
+-- Name: revisions_rev_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE revisions_rev_seq OWNED BY revisions.rev;
+
+
+--
+-- Name: objects; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE objects (
+    exported boolean DEFAULT true NOT NULL,
+    name text NOT NULL
+)
+INHERITS (revisions);
+
+
+--
+-- Name: accreq; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE accreq (
+)
+INHERITS (objects);
+
+
+--
+-- Name: attributes; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE attributes (
+    value text NOT NULL,
+    attr_key integer NOT NULL,
+    okey integer NOT NULL,
+    attr text NOT NULL
+);
+
+
+--
+-- Name: attributes_attr_key_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE attributes_attr_key_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+--
+-- Name: attributes_attr_key_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE attributes_attr_key_seq OWNED BY attributes.attr_key;
+
+
+--
+-- Name: accreq_attributes; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE accreq_attributes (
+)
+INHERITS (attributes);
+
+
+--
+-- Name: attributes_list; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE attributes_list (
+    canonical text NOT NULL,
+    ikey integer DEFAULT nextval('ikey_seq'::regclass) NOT NULL,
+    description text
+);
+
+
+--
+-- Name: COLUMN attributes_list.canonical; Type: COMMENT; Schema: public; Owner: -
+--
+
+COMMENT ON COLUMN attributes_list.canonical IS 'Nom de l''attribut';
+
+
+--
+-- Name: COLUMN attributes_list.ikey; Type: COMMENT; Schema: public; Owner: -
+--
+
+COMMENT ON COLUMN attributes_list.ikey IS 'Clef interne Ã  la base';
+
+
+--
+-- Name: accreq_attributes_list; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE accreq_attributes_list (
+    ikey integer,
+    rev integer DEFAULT nextval('revisions_rev_seq'::regclass) NOT NULL,
+    date timestamp with time zone DEFAULT now() NOT NULL,
+    "create" timestamp with time zone DEFAULT now() NOT NULL
+)
+INHERITS (attributes_list);
+
+
+--
+-- Name: address_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE address_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+--
+-- Name: address; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE address (
+    name text DEFAULT ('address'::text || (nextval('address_seq'::regclass))::text),
+    "user" text NOT NULL
+)
+INHERITS (objects);
+
+
+--
+-- Name: address_attributes_base; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE address_attributes_base (
+)
+INHERITS (attributes);
+
+
+--
+-- Name: address_attributes; Type: VIEW; Schema: public; Owner: -
+--
+
+CREATE VIEW address_attributes AS
+    (SELECT address."user" AS value, 'user' AS attr, address.rev AS attr_key, address.ikey AS okey FROM address UNION ALL SELECT address.name AS value, 'name' AS attr, address.rev AS attr_key, address.ikey AS okey FROM address) UNION ALL SELECT address_attributes.value, address_attributes.attr, address_attributes.attr_key, address_attributes.okey FROM address_attributes_base address_attributes;
+
+
+--
+-- Name: address_attributes_list; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE address_attributes_list (
+    rev integer,
+    date timestamp with time zone,
+    "create" timestamp with time zone
+)
+INHERITS (attributes_list, revisions);
+
+
+--
+-- Name: address_attributes_list_site; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE address_attributes_list_site (
+)
+INHERITS (address_attributes_list);
+
+
+--
+-- Name: address_attributes_site; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE address_attributes_site (
+)
+INHERITS (address_attributes_base);
+
+
+--
+-- Name: aliases; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE aliases (
+    forward text[],
+    expire timestamp with time zone
+)
+INHERITS (objects);
+
+
+--
+-- Name: TABLE aliases; Type: COMMENT; Schema: public; Owner: -
+--
+
+COMMENT ON TABLE aliases IS 'Mail aliases';
+
+
+--
+-- Name: user; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE "user" (
+    name text,
+    uidnumber integer NOT NULL,
+    gidnumber integer NOT NULL,
+    expire timestamp with time zone,
+    CONSTRAINT uid_not_empty CHECK ((name <> ''::text))
+)
+INHERITS (objects);
+
+
+--
+-- Name: aliases_attributes; Type: VIEW; Schema: public; Owner: -
+--
+
+CREATE VIEW aliases_attributes AS
+    SELECT "user".name AS value, 'user'::text AS attr, aliases.rev AS attr_key, aliases.ikey AS okey FROM (aliases JOIN "user" ON (((aliases.name = "user".name) OR (aliases.forward = ARRAY["user".name]))));
+
+
+--
+-- Name: aliases_attributes_list; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE aliases_attributes_list (
+    ikey integer,
+    canonical text,
+    description text
+)
+INHERITS (revisions, attributes_list);
+
+
+--
+-- Name: all_obj_attributes; Type: VIEW; Schema: public; Owner: -
+--
+
+CREATE VIEW all_obj_attributes AS
+    SELECT pg_class.relname, objects.name, attributes_list.description, attributes.attr_key, attributes.attr, attributes.value FROM (((attributes_list JOIN attributes ON ((attributes.attr = attributes_list.canonical))) JOIN pg_class ON ((attributes.tableoid = pg_class.oid))) JOIN objects ON ((objects.ikey = attributes.okey)));
+
+
+--
+-- Name: group; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE "group" (
+    name text,
+    gidnumber integer NOT NULL,
+    CONSTRAINT group_name_not_empty CHECK ((name <> ''::text))
+)
+INHERITS (objects);
+
+
+--
+-- Name: group_attributes_base; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE group_attributes_base (
+)
+INHERITS (attributes);
+
+
+--
+-- Name: user_attributes_base; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE user_attributes_base (
+)
+INHERITS (attributes);
+
+
+--
+-- Name: group_attributes; Type: VIEW; Schema: public; Owner: -
+--
+
+CREATE VIEW group_attributes AS
+    (((SELECT ("group".gidnumber)::text AS value, 'gidnumber' AS attr, "group".rev AS attr_key, "group".ikey AS okey FROM "group" UNION ALL SELECT "group".name AS value, 'name' AS attr, "group".rev AS attr_key, "group".ikey AS okey FROM "group") UNION ALL SELECT '1'::text AS value, 'exported' AS attr, "group".rev AS attr_key, "group".ikey AS okey FROM "group" WHERE ("group".exported = true)) UNION ALL SELECT group_attributes.value, group_attributes.attr, group_attributes.attr_key, group_attributes.okey FROM group_attributes_base group_attributes) UNION ALL SELECT "user".name AS value, 'memberUID' AS attr, user_attributes_base.attr_key, "group".ikey AS okey FROM ((user_attributes_base JOIN "user" ON (("user".ikey = user_attributes_base.okey))) JOIN "group" ON (("group".name = user_attributes_base.value))) WHERE (user_attributes_base.attr = 'contratType'::text);
+
+
+--
+-- Name: group_attributes_list; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE group_attributes_list (
+)
+INHERITS (revisions, attributes_list);
+
+
+--
+-- Name: group_attributes_sutypes; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE group_attributes_sutypes (
+)
+INHERITS (group_attributes_base);
+
+
+--
+-- Name: group_attributes_users; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE group_attributes_users (
+)
+INHERITS (group_attributes_base);
+
+
+--
+-- Name: nethost; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE nethost (
+)
+INHERITS (objects);
+
+
+--
+-- Name: nethost_attributes; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE nethost_attributes (
+)
+INHERITS (attributes);
+
+
+--
+-- Name: nethost_attributes_ips; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE nethost_attributes_ips (
+)
+INHERITS (nethost_attributes);
+
+
+--
+-- Name: nethost_attributes_list; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE nethost_attributes_list (
+    ikey integer,
+    canonical text,
+    description text
+)
+INHERITS (revisions, attributes_list);
+
+
+--
+-- Name: nethost_attributes_macs; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE nethost_attributes_macs (CONSTRAINT nethost_valid_mac_address CHECK (((value)::macaddr <> '00:00:00:00:00:00'::macaddr))
+)
+INHERITS (nethost_attributes);
+
+
+--
+-- Name: nethost_attributes_users; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE nethost_attributes_users (
+)
+INHERITS (nethost_attributes);
+
+
+--
+-- Name: netzone; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE netzone (
+)
+INHERITS (objects);
+
+
+--
+-- Name: netzone_attributes; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE netzone_attributes (
+)
+INHERITS (attributes);
+
+
+--
+-- Name: netzone_attributes_list; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE netzone_attributes_list (
+    ikey integer,
+    canonical text,
+    description text
+)
+INHERITS (revisions, attributes_list);
+
+
+--
+-- Name: netzone_attributes_netzone; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE netzone_attributes_netzone (
+)
+INHERITS (netzone_attributes);
+
+
+--
+-- Name: netzone_attributes_site; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE netzone_attributes_site (
+)
+INHERITS (netzone_attributes);
+
+
+--
+-- Name: objects_table; Type: VIEW; Schema: public; Owner: -
+--
+
+CREATE VIEW objects_table AS
+    SELECT objects.rev, objects.date, objects."create", objects.ikey, pg_class.relname, objects.name, objects.exported FROM (objects JOIN pg_class ON ((objects.tableoid = pg_class.oid)));
+
+
+--
+-- Name: revaliases; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE revaliases (
+    "as" text NOT NULL
+)
+INHERITS (objects);
+
+
+--
+-- Name: revisions_table; Type: VIEW; Schema: public; Owner: -
+--
+
+CREATE VIEW revisions_table AS
+    SELECT revisions.rev, revisions.date, revisions."create", revisions.ikey, pg_class.relname FROM (revisions JOIN pg_class ON ((revisions.tableoid = pg_class.oid)));
+
+
+--
+-- Name: settings; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE settings (
+    varname text NOT NULL,
+    val text
+)
+INHERITS (revisions);
+
+
+--
+-- Name: site; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE site (
+    name text
+)
+INHERITS (objects);
+
+
+--
+-- Name: site_attributes; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE site_attributes (
+)
+INHERITS (attributes);
+
+
+--
+-- Name: site_attributes_list; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE site_attributes_list (
+)
+INHERITS (revisions, attributes_list);
+
+
+--
+-- Name: su_type; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE su_type (
+    name text,
+    description text
+)
+INHERITS (objects);
+
+
+--
+-- Name: user_attributes; Type: VIEW; Schema: public; Owner: -
+--
+
+CREATE VIEW user_attributes AS
+    (((((((((((SELECT ("user".uidnumber)::text AS value, 'uidnumber' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM "user" UNION ALL SELECT ("user".gidnumber)::text AS value, 'gidnumber' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM "user") UNION ALL SELECT "user".name AS value, 'name' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM "user") UNION ALL SELECT ("user".expire)::text AS value, 'expire' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM "user" WHERE ("user".expire IS NOT NULL)) UNION ALL SELECT '1'::text AS value, 'exported' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM "user" WHERE ("user".exported = true)) UNION ALL SELECT address_attributes_site.value, 'allsite' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM (("user" JOIN address ON ((address."user" = "user".name))) JOIN address_attributes_site ON ((address_attributes_site.okey = address.ikey))) WHERE ((address_attributes_site.attr = 'site'::text) AND (address.exported = true))) UNION ALL SELECT "group".name AS value, 'memberOf' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM (("user" JOIN group_attributes ON ((group_attributes.value = "user".name))) JOIN "group" ON ((group_attributes.okey = "group".ikey))) WHERE (group_attributes.attr = 'memberUID'::text)) UNION ALL SELECT user_attributes.value, user_attributes.attr, user_attributes.attr_key, user_attributes.okey FROM user_attributes_base user_attributes) UNION ALL SELECT "group".name AS value, 'departments' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM ((("group" JOIN group_attributes_sutypes ON (("group".ikey = group_attributes_sutypes.okey))) JOIN group_attributes_users ON ((("group".ikey = group_attributes_users.okey) AND (group_attributes_users.attr = 'memberUID'::text)))) JOIN "user" ON (("user".name = group_attributes_users.value))) WHERE ((group_attributes_sutypes.value = 'dpmt'::text) AND (group_attributes_sutypes.attr = 'sutype'::text))) UNION ALL SELECT "group".name AS value, 'cells' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM ((("group" JOIN group_attributes_sutypes ON (("group".ikey = group_attributes_sutypes.okey))) JOIN group_attributes_users ON ((("group".ikey = group_attributes_users.okey) AND (group_attributes_users.attr = 'memberUID'::text)))) JOIN "user" ON (("user".name = group_attributes_users.value))) WHERE ((group_attributes_sutypes.value = 'cell'::text) AND (group_attributes_sutypes.attr = 'sutype'::text))) UNION ALL SELECT "group".name AS value, 'managedObjects' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM (("user" JOIN group_attributes ON ((group_attributes.value = "user".name))) JOIN "group" ON ((group_attributes.okey = "group".ikey))) WHERE (group_attributes.attr = 'managedBy'::text)) UNION ALL SELECT (justify_interval((now() - "user".expire)))::text AS value, 'expired' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM "user" WHERE ("user".expire <= now())) UNION ALL SELECT (1)::text AS value, 'active' AS attr, "user".rev AS attr_key, "user".ikey AS okey FROM "user" WHERE ((("user".expire IS NULL) OR ("user".expire >= now())) AND ("user".exported = true));
+
+
+--
+-- Name: user_attributes_groups; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE user_attributes_groups (
+)
+INHERITS (user_attributes_base);
+
+
+--
+-- Name: user_attributes_list; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE user_attributes_list (
+)
+INHERITS (revisions, attributes_list);
+
+
+--
+-- Name: user_attributes_site; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE user_attributes_site (
+)
+INHERITS (user_attributes_base);
+
+
+--
+-- Name: user_attributes_users; Type: TABLE; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE TABLE user_attributes_users (
+)
+INHERITS (user_attributes_base);
+
+
+--
+-- Name: attr_key; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE address_attributes ALTER COLUMN attr_key SET DEFAULT nextval('attributes_attr_key_seq'::regclass);
+
+
+--
+-- Name: attr_key; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE attributes ALTER COLUMN attr_key SET DEFAULT nextval('attributes_attr_key_seq'::regclass);
+
+
+--
+-- Name: attr_key; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE group_attributes ALTER COLUMN attr_key SET DEFAULT nextval('attributes_attr_key_seq'::regclass);
+
+
+--
+-- Name: rev; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE revisions ALTER COLUMN rev SET DEFAULT nextval('revisions_rev_seq'::regclass);
+
+
+--
+-- Name: attr_key; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE user_attributes ALTER COLUMN attr_key SET DEFAULT nextval('attributes_attr_key_seq'::regclass);
+
+
+--
+-- Name: acccreq_attributes_list_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY accreq_attributes_list
+    ADD CONSTRAINT acccreq_attributes_list_pkey PRIMARY KEY (canonical);
+
+
+--
+-- Name: accreq_attributes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY accreq_attributes
+    ADD CONSTRAINT accreq_attributes_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: accreq_ikey_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY accreq
+    ADD CONSTRAINT accreq_ikey_uniq UNIQUE (ikey);
+
+
+--
+-- Name: accreq_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY accreq
+    ADD CONSTRAINT accreq_pkey PRIMARY KEY (name);
+
+
+--
+-- Name: address_attr_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY address_attributes_base
+    ADD CONSTRAINT address_attr_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: address_attr_site_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY address_attributes_site
+    ADD CONSTRAINT address_attr_site_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: address_attributes_list_c_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY address_attributes_list
+    ADD CONSTRAINT address_attributes_list_c_uniq UNIQUE (canonical);
+
+
+--
+-- Name: address_attributes_list_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY address_attributes_list
+    ADD CONSTRAINT address_attributes_list_pkey PRIMARY KEY (ikey);
+
+
+--
+-- Name: address_attributes_list_site_c_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY address_attributes_list_site
+    ADD CONSTRAINT address_attributes_list_site_c_uniq UNIQUE (canonical);
+
+
+--
+-- Name: address_attributes_list_site_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY address_attributes_list_site
+    ADD CONSTRAINT address_attributes_list_site_pkey PRIMARY KEY (ikey);
+
+
+--
+-- Name: address_ikey_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY address
+    ADD CONSTRAINT address_ikey_uniq UNIQUE (ikey);
+
+
+--
+-- Name: address_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY address
+    ADD CONSTRAINT address_pkey PRIMARY KEY (name);
+
+
+--
+-- Name: aliases_attributes_list_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY aliases_attributes_list
+    ADD CONSTRAINT aliases_attributes_list_pkey PRIMARY KEY (ikey);
+
+
+--
+-- Name: aliases_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY aliases
+    ADD CONSTRAINT aliases_pkey PRIMARY KEY (name);
+
+
+--
+-- Name: attributes_list_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY attributes_list
+    ADD CONSTRAINT attributes_list_pkey PRIMARY KEY (ikey);
+
+
+--
+-- Name: attributes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY attributes
+    ADD CONSTRAINT attributes_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: g_attr_l_aliases_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY aliases_attributes_list
+    ADD CONSTRAINT g_attr_l_aliases_uniq UNIQUE (canonical);
+
+
+--
+-- Name: g_attr_l_c_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY group_attributes_list
+    ADD CONSTRAINT g_attr_l_c_uniq UNIQUE (canonical);
+
+
+--
+-- Name: g_attr_l_nethost_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY nethost_attributes_list
+    ADD CONSTRAINT g_attr_l_nethost_uniq UNIQUE (canonical);
+
+
+--
+-- Name: g_attr_l_netzone_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY netzone_attributes_list
+    ADD CONSTRAINT g_attr_l_netzone_uniq UNIQUE (canonical);
+
+
+--
+-- Name: g_attr_list_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY group_attributes_list
+    ADD CONSTRAINT g_attr_list_pkey PRIMARY KEY (ikey);
+
+
+--
+-- Name: goup_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY "group"
+    ADD CONSTRAINT goup_pkey PRIMARY KEY (name);
+
+
+--
+-- Name: group_attrbutes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY group_attributes_base
+    ADD CONSTRAINT group_attrbutes_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: group_attributes_sutypes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY group_attributes_sutypes
+    ADD CONSTRAINT group_attributes_sutypes_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: group_attributes_user_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY group_attributes_users
+    ADD CONSTRAINT group_attributes_user_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: group_attributes_users_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY group_attributes_users
+    ADD CONSTRAINT group_attributes_users_uniq UNIQUE (okey, attr, value);
+
+
+--
+-- Name: group_gid_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY "group"
+    ADD CONSTRAINT group_gid_uniq UNIQUE (gidnumber);
+
+
+--
+-- Name: group_ikey_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY "group"
+    ADD CONSTRAINT group_ikey_uniq UNIQUE (ikey);
+
+
+--
+-- Name: nethost_attributes_ip_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY nethost_attributes_ips
+    ADD CONSTRAINT nethost_attributes_ip_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: nethost_attributes_list_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY nethost_attributes_list
+    ADD CONSTRAINT nethost_attributes_list_pkey PRIMARY KEY (ikey);
+
+
+--
+-- Name: nethost_attributes_mac_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY nethost_attributes_macs
+    ADD CONSTRAINT nethost_attributes_mac_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: nethost_attributes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY nethost_attributes
+    ADD CONSTRAINT nethost_attributes_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: nethost_attributes_user_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY nethost_attributes_users
+    ADD CONSTRAINT nethost_attributes_user_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: nethost_attributes_users_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY nethost_attributes_users
+    ADD CONSTRAINT nethost_attributes_users_uniq UNIQUE (okey, attr, value);
+
+
+--
+-- Name: nethost_ikey_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY nethost
+    ADD CONSTRAINT nethost_ikey_uniq UNIQUE (ikey);
+
+
+--
+-- Name: nethost_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY nethost
+    ADD CONSTRAINT nethost_pkey PRIMARY KEY (name);
+
+
+--
+-- Name: netzone_attr_netzone_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY netzone_attributes_netzone
+    ADD CONSTRAINT netzone_attr_netzone_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: netzone_attr_site_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY netzone_attributes_site
+    ADD CONSTRAINT netzone_attr_site_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: netzone_attributes_list_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY netzone_attributes_list
+    ADD CONSTRAINT netzone_attributes_list_pkey PRIMARY KEY (ikey);
+
+
+--
+-- Name: netzone_attributes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY netzone_attributes
+    ADD CONSTRAINT netzone_attributes_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: netzone_ikey_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY netzone
+    ADD CONSTRAINT netzone_ikey_uniq UNIQUE (ikey);
+
+
+--
+-- Name: netzone_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY netzone
+    ADD CONSTRAINT netzone_pkey PRIMARY KEY (name);
+
+
+--
+-- Name: objects_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY objects
+    ADD CONSTRAINT objects_pkey PRIMARY KEY (ikey);
+
+
+--
+-- Name: p_attr_l_c_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY user_attributes_list
+    ADD CONSTRAINT p_attr_l_c_uniq UNIQUE (canonical);
+
+
+--
+-- Name: p_attr_list_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY user_attributes_list
+    ADD CONSTRAINT p_attr_list_pkey PRIMARY KEY (ikey);
+
+
+--
+-- Name: person_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY "user"
+    ADD CONSTRAINT person_pkey PRIMARY KEY (name);
+
+
+--
+-- Name: person_uid_key; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY "user"
+    ADD CONSTRAINT person_uid_key UNIQUE (uidnumber);
+
+
+--
+-- Name: revaliases_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY revaliases
+    ADD CONSTRAINT revaliases_pkey PRIMARY KEY (name);
+
+
+--
+-- Name: revisions_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY revisions
+    ADD CONSTRAINT revisions_pkey PRIMARY KEY (ikey);
+
+
+--
+-- Name: s_attr_l_c_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY site_attributes_list
+    ADD CONSTRAINT s_attr_l_c_uniq UNIQUE (canonical);
+
+
+--
+-- Name: s_attr_list_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY site_attributes_list
+    ADD CONSTRAINT s_attr_list_pkey PRIMARY KEY (ikey);
+
+
+--
+-- Name: settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY settings
+    ADD CONSTRAINT settings_pkey PRIMARY KEY (varname);
+
+
+--
+-- Name: site_attributes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY site_attributes
+    ADD CONSTRAINT site_attributes_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: site_okey_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY site
+    ADD CONSTRAINT site_okey_uniq UNIQUE (ikey);
+
+
+--
+-- Name: site_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY site
+    ADD CONSTRAINT site_pkey PRIMARY KEY (name);
+
+
+--
+-- Name: su_type_name_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY su_type
+    ADD CONSTRAINT su_type_name_uniq UNIQUE (name);
+
+
+--
+-- Name: su_type_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY su_type
+    ADD CONSTRAINT su_type_pkey PRIMARY KEY (ikey);
+
+
+--
+-- Name: user_attr_groups_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY user_attributes_groups
+    ADD CONSTRAINT user_attr_groups_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: user_attr_site_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY user_attributes_site
+    ADD CONSTRAINT user_attr_site_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: user_attr_user_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY user_attributes_users
+    ADD CONSTRAINT user_attr_user_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: user_attributes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY user_attributes_base
+    ADD CONSTRAINT user_attributes_pkey PRIMARY KEY (attr_key);
+
+
+--
+-- Name: user_ikey_uniq; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY "user"
+    ADD CONSTRAINT user_ikey_uniq UNIQUE (ikey);
+
+
+--
+-- Name: address_attr_base_attr_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX address_attr_base_attr_idx ON address_attributes_base USING btree (attr);
+
+
+--
+-- Name: address_exported_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX address_exported_idx ON address USING btree (exported);
+
+
+--
+-- Name: aliases_exported_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX aliases_exported_idx ON aliases USING btree (exported);
+
+
+--
+-- Name: aliases_forwards_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX aliases_forwards_idx ON aliases USING gin (forward);
+
+
+--
+-- Name: aliases_forwards_lower_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX aliases_forwards_lower_idx ON aliases USING gin (string_to_array(lower(array_to_string(forward, ','::text)), ','::text));
+
+
+--
+-- Name: aliases_name_lower_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX aliases_name_lower_idx ON aliases USING btree (lower(name));
+
+
+--
+-- Name: aliases_rev_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX aliases_rev_idx ON aliases USING btree (rev);
+
+
+--
+-- Name: fki_accreq_attr_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_accreq_attr_okey_fkey ON accreq_attributes USING btree (okey);
+
+
+--
+-- Name: fki_accreq_attributes_list_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_accreq_attributes_list_attr_fkey ON accreq_attributes USING btree (attr);
+
+
+--
+-- Name: fki_address_attr_site_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_address_attr_site_okey_fkey ON address_attributes_site USING btree (okey);
+
+
+--
+-- Name: fki_address_attributes_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_address_attributes_okey_fkey ON address_attributes_base USING btree (okey);
+
+
+--
+-- Name: fki_address_attributes_site_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_address_attributes_site_attr_fkey ON address_attributes_site USING btree (attr);
+
+
+--
+-- Name: fki_address_attributes_site_value_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_address_attributes_site_value_fkey ON address_attributes_site USING btree (value);
+
+
+--
+-- Name: fki_address_user_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_address_user_fkey ON address USING btree ("user");
+
+
+--
+-- Name: fki_group_attr_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_group_attr_okey_fkey ON group_attributes_base USING btree (okey);
+
+
+--
+-- Name: fki_group_attr_sutypes_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_group_attr_sutypes_okey_fkey ON group_attributes_sutypes USING btree (okey);
+
+
+--
+-- Name: fki_group_attr_users_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_group_attr_users_okey_fkey ON group_attributes_users USING btree (okey);
+
+
+--
+-- Name: fki_group_attributes_sutypes_attr; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_group_attributes_sutypes_attr ON group_attributes_sutypes USING btree (attr);
+
+
+--
+-- Name: fki_group_attributes_sutypes_sutype_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_group_attributes_sutypes_sutype_fkey ON group_attributes_sutypes USING btree (value);
+
+
+--
+-- Name: fki_group_attributes_users_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_group_attributes_users_attr_fkey ON group_attributes_users USING btree (attr);
+
+
+--
+-- Name: fki_group_attributes_users_user_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_group_attributes_users_user_fkey ON group_attributes_users USING btree (value);
+
+
+--
+-- Name: fki_nethost_attr_ip_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_nethost_attr_ip_fkey ON nethost_attributes_ips USING btree (okey);
+
+
+--
+-- Name: fki_nethost_attr_mac_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_nethost_attr_mac_fkey ON nethost_attributes_macs USING btree (okey);
+
+
+--
+-- Name: fki_nethost_attr_users_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_nethost_attr_users_okey_fkey ON nethost_attributes_users USING btree (okey);
+
+
+--
+-- Name: fki_nethost_attributes_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_nethost_attributes_attr_fkey ON nethost_attributes USING btree (attr);
+
+
+--
+-- Name: fki_nethost_attributes_users_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_nethost_attributes_users_attr_fkey ON nethost_attributes_users USING btree (attr);
+
+
+--
+-- Name: fki_nethost_attributes_users_user_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_nethost_attributes_users_user_fkey ON nethost_attributes_users USING btree (value);
+
+
+--
+-- Name: fki_netzone_attr_netzone_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_netzone_attr_netzone_okey_fkey ON netzone_attributes_netzone USING btree (okey);
+
+
+--
+-- Name: fki_netzone_attr_site_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_netzone_attr_site_okey_fkey ON netzone_attributes_site USING btree (okey);
+
+
+--
+-- Name: fki_netzone_attributes_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_netzone_attributes_attr_fkey ON netzone_attributes USING btree (attr);
+
+
+--
+-- Name: fki_netzone_attributes_netzone_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_netzone_attributes_netzone_attr_fkey ON netzone_attributes_netzone USING btree (attr);
+
+
+--
+-- Name: fki_netzone_attributes_netzone_value_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_netzone_attributes_netzone_value_fkey ON netzone_attributes_netzone USING btree (value);
+
+
+--
+-- Name: fki_netzone_attributes_site_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_netzone_attributes_site_attr_fkey ON netzone_attributes_site USING btree (attr);
+
+
+--
+-- Name: fki_netzone_attributes_site_value_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_netzone_attributes_site_value_fkey ON netzone_attributes_site USING btree (value);
+
+
+--
+-- Name: fki_revaliase_name_user_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_revaliase_name_user_fkey ON revaliases USING btree (name);
+
+
+--
+-- Name: fki_revaliases_user_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_revaliases_user_fkey ON revaliases USING btree (name);
+
+
+--
+-- Name: fki_site_attributes_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_site_attributes_attr_fkey ON site_attributes USING btree (attr);
+
+
+--
+-- Name: fki_user_attr_groups_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_user_attr_groups_okey_fkey ON user_attributes_groups USING btree (okey);
+
+
+--
+-- Name: fki_user_attr_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_user_attr_okey_fkey ON user_attributes_base USING btree (okey);
+
+
+--
+-- Name: fki_user_attr_site_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_user_attr_site_okey_fkey ON user_attributes_site USING btree (okey);
+
+
+--
+-- Name: fki_user_attr_users_okey_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_user_attr_users_okey_fkey ON user_attributes_users USING btree (okey);
+
+
+--
+-- Name: fki_user_attributes_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_user_attributes_attr_fkey ON user_attributes_base USING btree (attr);
+
+
+--
+-- Name: fki_user_attributes_groups_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_user_attributes_groups_attr_fkey ON user_attributes_groups USING btree (attr);
+
+
+--
+-- Name: fki_user_attributes_groups_value_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_user_attributes_groups_value_fkey ON user_attributes_groups USING btree (value);
+
+
+--
+-- Name: fki_user_attributes_site_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_user_attributes_site_attr_fkey ON user_attributes_site USING btree (attr);
+
+
+--
+-- Name: fki_user_attributes_site_value_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_user_attributes_site_value_fkey ON user_attributes_site USING btree (value);
+
+
+--
+-- Name: fki_user_attributes_users_attr_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_user_attributes_users_attr_fkey ON user_attributes_users USING btree (attr);
+
+
+--
+-- Name: fki_user_attributes_users_value_fkey; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX fki_user_attributes_users_value_fkey ON user_attributes_users USING btree (value);
+
+
+--
+-- Name: group_attributes_base_value_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX group_attributes_base_value_idx ON group_attributes_base USING btree (value);
+
+
+--
+-- Name: group_exported_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX group_exported_idx ON "group" USING btree (exported);
+
+
+--
+-- Name: group_rev_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX group_rev_idx ON "group" USING btree (rev);
+
+
+--
+-- Name: nethost_attr_value_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX nethost_attr_value_idx ON nethost_attributes USING btree (value);
+
+
+--
+-- Name: nethost_attributes_ip_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX nethost_attributes_ip_idx ON nethost_attributes_ips USING btree (((value)::inet));
+
+
+--
+-- Name: nethost_attributes_mac_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX nethost_attributes_mac_idx ON nethost_attributes_macs USING btree (((value)::macaddr));
+
+
+--
+-- Name: nethost_cname_uniq_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX nethost_cname_uniq_idx ON nethost_attributes USING btree (value) WHERE (attr = 'cname'::text);
+
+
+--
+-- Name: netzone_attr_value_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX netzone_attr_value_idx ON netzone_attributes USING btree (value);
+
+
+--
+-- Name: netzone_attributes_net_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX netzone_attributes_net_idx ON netzone_attributes USING btree (((value)::inet), attr, okey) WHERE (attr = 'net'::text);
+
+
+--
+-- Name: netzone_attributes_netexclude_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX netzone_attributes_netexclude_idx ON netzone_attributes USING btree (((value)::inet), attr, okey) WHERE (attr = 'netExclude'::text);
+
+
+--
+-- Name: person_rev_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX person_rev_idx ON "user" USING btree (rev);
+
+
+--
+-- Name: revaliases_as_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX revaliases_as_idx ON revaliases USING btree ("as");
+
+
+--
+-- Name: revaliases_as_lower_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX revaliases_as_lower_idx ON revaliases USING btree (lower("as"));
+
+
+--
+-- Name: revaliases_exported_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX revaliases_exported_idx ON revaliases USING btree (exported);
+
+
+--
+-- Name: revaliases_rev_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX revaliases_rev_idx ON revaliases USING btree (rev);
+
+
+--
+-- Name: site_attr_value_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX site_attr_value_idx ON site_attributes USING btree (value);
+
+
+--
+-- Name: site_exported_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX site_exported_idx ON site USING btree (exported);
+
+
+--
+-- Name: site_rev_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX site_rev_idx ON site USING btree (rev);
+
+
+--
+-- Name: user_attr_value_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX user_attr_value_idx ON user_attributes_base USING btree (value);
+
+
+--
+-- Name: user_expire_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX user_expire_idx ON "user" USING btree (expire);
+
+
+--
+-- Name: user_exported_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX user_exported_idx ON "user" USING btree (exported);
+
+
+--
+-- Name: address_attributes_insert; Type: RULE; Schema: public; Owner: -
+--
+
+CREATE RULE address_attributes_insert AS ON INSERT TO address_attributes DO INSTEAD INSERT INTO address_attributes_base (value, attr, attr_key, okey) VALUES (new.value, new.attr, new.attr_key, new.okey);
+
+
+--
+-- Name: address_attributes_update; Type: RULE; Schema: public; Owner: -
+--
+
+CREATE RULE address_attributes_update AS ON UPDATE TO address_attributes DO INSTEAD UPDATE address_attributes_base SET value = new.value, attr = new.attr, attr_key = new.attr_key, okey = new.okey WHERE ((address_attributes_base.attr_key = old.attr_key) AND (address_attributes_base.attr = old.attr));
+
+
+--
+-- Name: address_atttributes_delete; Type: RULE; Schema: public; Owner: -
+--
+
+CREATE RULE address_atttributes_delete AS ON DELETE TO address_attributes DO INSTEAD DELETE FROM address_attributes_base WHERE ((address_attributes_base.attr_key = old.attr_key) AND (address_attributes_base.attr = old.attr));
+
+
+--
+-- Name: group_attributes_insert; Type: RULE; Schema: public; Owner: -
+--
+
+CREATE RULE group_attributes_insert AS ON INSERT TO group_attributes DO INSTEAD INSERT INTO group_attributes_base (value, attr, attr_key, okey) VALUES (new.value, new.attr, new.attr_key, new.okey);
+
+
+--
+-- Name: group_attributes_update; Type: RULE; Schema: public; Owner: -
+--
+
+CREATE RULE group_attributes_update AS ON UPDATE TO group_attributes DO INSTEAD UPDATE group_attributes_base SET value = new.value, attr = new.attr, attr_key = new.attr_key, okey = new.okey WHERE ((group_attributes_base.attr_key = old.attr_key) AND (group_attributes_base.attr = old.attr));
+
+
+--
+-- Name: group_atttributes_delete; Type: RULE; Schema: public; Owner: -
+--
+
+CREATE RULE group_atttributes_delete AS ON DELETE TO group_attributes DO INSTEAD DELETE FROM group_attributes_base WHERE ((group_attributes_base.attr_key = old.attr_key) AND (group_attributes_base.attr = old.attr));
+
+
+--
+-- Name: user_attributes_insert; Type: RULE; Schema: public; Owner: -
+--
+
+CREATE RULE user_attributes_insert AS ON INSERT TO user_attributes DO INSTEAD INSERT INTO user_attributes_base (value, attr, attr_key, okey) VALUES (new.value, new.attr, new.attr_key, new.okey);
+
+
+--
+-- Name: user_attributes_update; Type: RULE; Schema: public; Owner: -
+--
+
+CREATE RULE user_attributes_update AS ON UPDATE TO user_attributes DO INSTEAD UPDATE user_attributes_base SET value = new.value, attr = new.attr, attr_key = new.attr_key, okey = new.okey WHERE ((user_attributes_base.attr_key = old.attr_key) AND (user_attributes_base.attr = old.attr));
+
+
+--
+-- Name: user_atttributes_delete; Type: RULE; Schema: public; Owner: -
+--
+
+CREATE RULE user_atttributes_delete AS ON DELETE TO user_attributes DO INSTEAD DELETE FROM user_attributes_base WHERE ((user_attributes_base.attr_key = old.attr_key) AND (user_attributes_base.attr = old.attr));
+
+
+--
+-- Name: accreq_update_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER accreq_update_rev_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON accreq
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: address_attr_update_ref_a_s; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER address_attr_update_ref_a_s
+    AFTER INSERT OR DELETE OR UPDATE ON address_attributes_site
+    FOR EACH ROW
+    EXECUTE PROCEDURE address_attr_update_ref();
+
+
+--
+-- Name: address_attr_update_ref_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER address_attr_update_ref_tg
+    AFTER INSERT OR DELETE OR UPDATE ON address_attributes_base
+    FOR EACH ROW
+    EXECUTE PROCEDURE address_attr_update_ref();
+
+
+--
+-- Name: address_sort_fields_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER address_sort_fields_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON address_attributes_base
+    FOR EACH ROW
+    EXECUTE PROCEDURE address_sort_fields();
+
+
+--
+-- Name: address_update_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER address_update_rev_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON address
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: address_update_user_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER address_update_user_rev_tg
+    AFTER INSERT OR DELETE OR UPDATE ON address
+    FOR EACH ROW
+    EXECUTE PROCEDURE address_update_user_rev();
+
+
+--
+-- Name: aliases_nickname_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER aliases_nickname_tg
+    AFTER DELETE OR UPDATE ON aliases
+    FOR EACH ROW
+    EXECUTE PROCEDURE aliases_nickname();
+
+
+--
+-- Name: aliases_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER aliases_rev_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON aliases
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: department_group_upd_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER department_group_upd_tg
+    AFTER INSERT OR UPDATE ON user_attributes_groups
+    FOR EACH ROW
+    EXECUTE PROCEDURE department_group_upd_f();
+
+
+--
+-- Name: fmt_macaddr_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER fmt_macaddr_tg
+    BEFORE INSERT OR UPDATE ON nethost_attributes_macs
+    FOR EACH ROW
+    EXECUTE PROCEDURE fmt_macaddr();
+
+
+--
+-- Name: group_attr_update; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER group_attr_update
+    AFTER INSERT OR DELETE OR UPDATE ON group_attributes_base
+    FOR EACH ROW
+    EXECUTE PROCEDURE group_attr_update_ref();
+
+
+--
+-- Name: group_attr_user_update_user_ref_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER group_attr_user_update_user_ref_tg
+    AFTER INSERT OR DELETE OR UPDATE ON group_attributes_users
+    FOR EACH ROW
+    EXECUTE PROCEDURE group_attr_user_update_user_ref();
+
+
+--
+-- Name: group_attributes_sutypes_update_ref; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER group_attributes_sutypes_update_ref
+    AFTER INSERT OR DELETE OR UPDATE ON group_attributes_sutypes
+    FOR EACH ROW
+    EXECUTE PROCEDURE group_attr_update_ref();
+
+
+--
+-- Name: group_attributes_users_update_ref; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER group_attributes_users_update_ref
+    AFTER INSERT OR DELETE OR UPDATE ON group_attributes_users
+    FOR EACH ROW
+    EXECUTE PROCEDURE group_attr_update_ref();
+
+
+--
+-- Name: group_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER group_rev_tg
+    BEFORE DELETE OR UPDATE ON "group"
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: group_sort_field_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER group_sort_field_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON group_attributes_base
+    FOR EACH ROW
+    EXECUTE PROCEDURE group_sort_fields();
+
+
+--
+-- Name: nethost_attr_ip_update; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER nethost_attr_ip_update
+    AFTER INSERT OR DELETE OR UPDATE ON nethost_attributes_ips
+    FOR EACH ROW
+    EXECUTE PROCEDURE nethost_attr_update_ref();
+
+
+--
+-- Name: nethost_attr_mac_update; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER nethost_attr_mac_update
+    AFTER INSERT OR DELETE OR UPDATE ON nethost_attributes_macs
+    FOR EACH ROW
+    EXECUTE PROCEDURE nethost_attr_update_ref();
+
+
+--
+-- Name: nethost_attr_update; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER nethost_attr_update
+    AFTER INSERT OR DELETE OR UPDATE ON nethost_attributes
+    FOR EACH ROW
+    EXECUTE PROCEDURE nethost_attr_update_ref();
+
+
+--
+-- Name: nethost_attributes_users_update_ref; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER nethost_attributes_users_update_ref
+    AFTER INSERT OR DELETE OR UPDATE ON nethost_attributes_users
+    FOR EACH ROW
+    EXECUTE PROCEDURE nethost_attr_update_ref();
+
+
+--
+-- Name: nethost_sort_field_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER nethost_sort_field_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON nethost_attributes
+    FOR EACH ROW
+    EXECUTE PROCEDURE nethost_sort_fields();
+
+
+--
+-- Name: nethost_upd_zone_tg_del; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER nethost_upd_zone_tg_del
+    BEFORE DELETE ON nethost
+    FOR EACH ROW
+    EXECUTE PROCEDURE nethost_upd_zone();
+
+
+--
+-- Name: nethost_upd_zone_tg_insupd; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER nethost_upd_zone_tg_insupd
+    AFTER INSERT OR UPDATE ON nethost
+    FOR EACH ROW
+    EXECUTE PROCEDURE nethost_upd_zone();
+
+
+--
+-- Name: nethost_update_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER nethost_update_rev_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON nethost
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: nethostipdel_upd_zone_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER nethostipdel_upd_zone_tg
+    BEFORE DELETE ON nethost_attributes_ips
+    FOR EACH ROW
+    EXECUTE PROCEDURE nethostipdel_upd_zone();
+
+
+--
+-- Name: netzone_attr_update; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER netzone_attr_update
+    AFTER INSERT OR DELETE OR UPDATE ON netzone_attributes
+    FOR EACH ROW
+    EXECUTE PROCEDURE netzone_attr_update_ref();
+
+
+--
+-- Name: netzone_attr_update_ref_a_s; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER netzone_attr_update_ref_a_s
+    AFTER INSERT OR DELETE OR UPDATE ON netzone_attributes_site
+    FOR EACH ROW
+    EXECUTE PROCEDURE netzone_attr_update_ref();
+
+
+--
+-- Name: netzone_attr_update_ref_a_s; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER netzone_attr_update_ref_a_s
+    AFTER INSERT OR DELETE OR UPDATE ON netzone_attributes_netzone
+    FOR EACH ROW
+    EXECUTE PROCEDURE netzone_attr_update_ref();
+
+
+--
+-- Name: netzone_sort_fields_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER netzone_sort_fields_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON netzone_attributes
+    FOR EACH ROW
+    EXECUTE PROCEDURE netzone_sort_fields();
+
+
+--
+-- Name: netzone_update_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER netzone_update_rev_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON netzone
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: person_attr_update; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER person_attr_update
+    AFTER INSERT OR DELETE OR UPDATE ON user_attributes_base
+    FOR EACH ROW
+    EXECUTE PROCEDURE person_attr_update_ref();
+
+
+--
+-- Name: person_attr_update_ref_a_g; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER person_attr_update_ref_a_g
+    AFTER INSERT OR DELETE OR UPDATE ON user_attributes_groups
+    FOR EACH ROW
+    EXECUTE PROCEDURE person_attr_update_ref();
+
+
+--
+-- Name: person_attr_update_ref_a_s; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER person_attr_update_ref_a_s
+    AFTER INSERT OR DELETE OR UPDATE ON user_attributes_site
+    FOR EACH ROW
+    EXECUTE PROCEDURE person_attr_update_ref();
+
+
+--
+-- Name: person_attr_update_ref_a_u; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER person_attr_update_ref_a_u
+    AFTER INSERT OR DELETE OR UPDATE ON user_attributes_users
+    FOR EACH ROW
+    EXECUTE PROCEDURE person_attr_update_ref();
+
+
+--
+-- Name: person_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER person_rev_tg
+    BEFORE DELETE OR UPDATE ON "user"
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: revaliases_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER revaliases_rev_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON revaliases
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: revisions_rev_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER revisions_rev_tg
+    AFTER DELETE OR UPDATE ON revisions
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: settings_revision_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER settings_revision_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON settings
+    FOR EACH ROW
+    EXECUTE PROCEDURE rev_tg_f();
+
+
+--
+-- Name: site_attr_update; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER site_attr_update
+    AFTER INSERT OR DELETE OR UPDATE ON site_attributes
+    FOR EACH ROW
+    EXECUTE PROCEDURE site_attr_update_ref();
+
+
+--
+-- Name: update_address_site_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER update_address_site_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON site
+    FOR EACH ROW
+    EXECUTE PROCEDURE update_address_site();
+
+
+--
+-- Name: user_sort_field_tg; Type: TRIGGER; Schema: public; Owner: -
+--
+
+CREATE TRIGGER user_sort_field_tg
+    BEFORE INSERT OR DELETE OR UPDATE ON user_attributes_base
+    FOR EACH ROW
+    EXECUTE PROCEDURE user_sort_fields();
+
+
+--
+-- Name: accreq_attr_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY accreq_attributes
+    ADD CONSTRAINT accreq_attr_okey_fkey FOREIGN KEY (okey) REFERENCES accreq(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: accreq_attributes_list_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY accreq_attributes
+    ADD CONSTRAINT accreq_attributes_list_attr_fkey FOREIGN KEY (attr) REFERENCES accreq_attributes_list(canonical);
+
+
+--
+-- Name: address_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY address_attributes_base
+    ADD CONSTRAINT address_attr_fkey FOREIGN KEY (attr) REFERENCES address_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: address_attributes_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY address_attributes_base
+    ADD CONSTRAINT address_attributes_okey_fkey FOREIGN KEY (okey) REFERENCES address(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: address_attributes_site_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY address_attributes_site
+    ADD CONSTRAINT address_attributes_site_attr_fkey FOREIGN KEY (attr) REFERENCES address_attributes_list_site(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: address_attributes_site_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY address_attributes_site
+    ADD CONSTRAINT address_attributes_site_okey_fkey FOREIGN KEY (okey) REFERENCES address(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: address_attributes_site_value_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY address_attributes_site
+    ADD CONSTRAINT address_attributes_site_value_fkey FOREIGN KEY (value) REFERENCES site(name) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
+
+
+--
+-- Name: address_user_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY address
+    ADD CONSTRAINT address_user_fkey FOREIGN KEY ("user") REFERENCES "user"(name) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: group_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY group_attributes_base
+    ADD CONSTRAINT group_attr_fkey FOREIGN KEY (attr) REFERENCES group_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: group_attr_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY group_attributes_base
+    ADD CONSTRAINT group_attr_okey_fkey FOREIGN KEY (okey) REFERENCES "group"(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: group_attr_sutypes_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY group_attributes_sutypes
+    ADD CONSTRAINT group_attr_sutypes_okey_fkey FOREIGN KEY (okey) REFERENCES "group"(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: group_attr_users_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY group_attributes_users
+    ADD CONSTRAINT group_attr_users_okey_fkey FOREIGN KEY (okey) REFERENCES "group"(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: group_attributes_sutypes_attr; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY group_attributes_sutypes
+    ADD CONSTRAINT group_attributes_sutypes_attr FOREIGN KEY (attr) REFERENCES group_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: group_attributes_sutypes_sutype_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY group_attributes_sutypes
+    ADD CONSTRAINT group_attributes_sutypes_sutype_fkey FOREIGN KEY (value) REFERENCES su_type(name) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: group_attributes_users_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY group_attributes_users
+    ADD CONSTRAINT group_attributes_users_attr_fkey FOREIGN KEY (attr) REFERENCES group_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: group_attributes_users_user_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY group_attributes_users
+    ADD CONSTRAINT group_attributes_users_user_fkey FOREIGN KEY (value) REFERENCES "user"(name) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: nethost_attr_ip_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY nethost_attributes_ips
+    ADD CONSTRAINT nethost_attr_ip_fkey FOREIGN KEY (okey) REFERENCES nethost(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: nethost_attr_mac_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY nethost_attributes_macs
+    ADD CONSTRAINT nethost_attr_mac_fkey FOREIGN KEY (okey) REFERENCES nethost(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: nethost_attr_users_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY nethost_attributes
+    ADD CONSTRAINT nethost_attr_users_okey_fkey FOREIGN KEY (okey) REFERENCES nethost(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: nethost_attr_users_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY nethost_attributes_users
+    ADD CONSTRAINT nethost_attr_users_okey_fkey FOREIGN KEY (okey) REFERENCES nethost(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: nethost_attributes_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY nethost_attributes
+    ADD CONSTRAINT nethost_attributes_attr_fkey FOREIGN KEY (attr) REFERENCES nethost_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: nethost_attributes_users_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY nethost_attributes_users
+    ADD CONSTRAINT nethost_attributes_users_attr_fkey FOREIGN KEY (attr) REFERENCES nethost_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: nethost_attributes_users_user_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY nethost_attributes_users
+    ADD CONSTRAINT nethost_attributes_users_user_fkey FOREIGN KEY (value) REFERENCES "user"(name) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: netzone_attr_users_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY netzone_attributes
+    ADD CONSTRAINT netzone_attr_users_okey_fkey FOREIGN KEY (okey) REFERENCES netzone(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: netzone_attributes_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY netzone_attributes
+    ADD CONSTRAINT netzone_attributes_attr_fkey FOREIGN KEY (attr) REFERENCES netzone_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: netzone_attributes_netzone_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY netzone_attributes_netzone
+    ADD CONSTRAINT netzone_attributes_netzone_attr_fkey FOREIGN KEY (attr) REFERENCES netzone_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: netzone_attributes_netzone_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY netzone_attributes_netzone
+    ADD CONSTRAINT netzone_attributes_netzone_okey_fkey FOREIGN KEY (okey) REFERENCES netzone(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: netzone_attributes_netzone_value_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY netzone_attributes_netzone
+    ADD CONSTRAINT netzone_attributes_netzone_value_fkey FOREIGN KEY (value) REFERENCES netzone(name) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
+
+
+--
+-- Name: netzone_attributes_site_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY netzone_attributes_site
+    ADD CONSTRAINT netzone_attributes_site_attr_fkey FOREIGN KEY (attr) REFERENCES netzone_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: netzone_attributes_site_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY netzone_attributes_site
+    ADD CONSTRAINT netzone_attributes_site_okey_fkey FOREIGN KEY (okey) REFERENCES netzone(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: netzone_attributes_site_value_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY netzone_attributes_site
+    ADD CONSTRAINT netzone_attributes_site_value_fkey FOREIGN KEY (value) REFERENCES site(name) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
+
+
+--
+-- Name: revaliase_name_user_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY revaliases
+    ADD CONSTRAINT revaliase_name_user_fkey FOREIGN KEY (name) REFERENCES "user"(name) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: site_attr_users_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY site_attributes
+    ADD CONSTRAINT site_attr_users_okey_fkey FOREIGN KEY (okey) REFERENCES site(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: site_attributes_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY site_attributes
+    ADD CONSTRAINT site_attributes_attr_fkey FOREIGN KEY (attr) REFERENCES site_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: user_attr_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY user_attributes_base
+    ADD CONSTRAINT user_attr_okey_fkey FOREIGN KEY (okey) REFERENCES "user"(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: user_attributes_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY user_attributes_base
+    ADD CONSTRAINT user_attributes_attr_fkey FOREIGN KEY (attr) REFERENCES user_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: user_attributes_groups_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY user_attributes_groups
+    ADD CONSTRAINT user_attributes_groups_attr_fkey FOREIGN KEY (attr) REFERENCES user_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: user_attributes_groups_value_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY user_attributes_groups
+    ADD CONSTRAINT user_attributes_groups_value_fkey FOREIGN KEY (value) REFERENCES "group"(name) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
+
+
+--
+-- Name: user_attributes_site_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY user_attributes_site
+    ADD CONSTRAINT user_attributes_site_attr_fkey FOREIGN KEY (attr) REFERENCES user_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: user_attributes_site_value_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY user_attributes_site
+    ADD CONSTRAINT user_attributes_site_value_fkey FOREIGN KEY (value) REFERENCES site(name) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
+
+
+--
+-- Name: user_attributes_users_attr_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY user_attributes_users
+    ADD CONSTRAINT user_attributes_users_attr_fkey FOREIGN KEY (attr) REFERENCES user_attributes_list(canonical) ON UPDATE CASCADE;
+
+
+--
+-- Name: user_attributes_users_value_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY user_attributes_users
+    ADD CONSTRAINT user_attributes_users_value_fkey FOREIGN KEY (value) REFERENCES "user"(name) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
+
+
+--
+-- Name: user_site_attr_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY user_attributes_site
+    ADD CONSTRAINT user_site_attr_okey_fkey FOREIGN KEY (okey) REFERENCES "user"(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: user_users_attr_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY user_attributes_users
+    ADD CONSTRAINT user_users_attr_okey_fkey FOREIGN KEY (okey) REFERENCES "user"(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- Name: user_users_attr_okey_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY user_attributes_groups
+    ADD CONSTRAINT user_users_attr_okey_fkey FOREIGN KEY (okey) REFERENCES "user"(ikey) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- PostgreSQL database dump complete
+--
+
+INSERT INTO settings (varname, val) VALUES ('schema_version', '3');
+
Index: tags/5.2.23/LATMOS-Accounts/t/01_pod.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/01_pod.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/01_pod.t	(revision 2119)
@@ -0,0 +1,19 @@
+use strict;
+use warnings;
+use Test::More;
+use Test::Pod;
+
+eval "use Pod::Checker";
+plan skip_all => 'Pod::Checker required' if $@;
+
+my @pods = (
+    all_pod_files('blib', 'man'),
+);
+
+plan tests => scalar(@pods) * 2;
+
+foreach(@pods) {
+    my $errors = podchecker($_);
+    ok($errors >= 0, "File $_ contains pod");
+    ok($errors <= 0, "Check Pod of $_");
+}
Index: tags/5.2.23/LATMOS-Accounts/t/01_pod_coverage.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/01_pod_coverage.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/01_pod_coverage.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More;
+
+my $trustparents = { coverage_class => 'Pod::Coverage::CountParents' };
+
+eval "use Test::Pod::Coverage 1.00";
+plan skip_all => "Test::Pod::Coverage 1.00 required for testing POD coverage" if $@;
+
+all_pod_coverage_ok( $trustparents );
Index: tags/5.2.23/LATMOS-Accounts/t/02_LATMOS-Accounts.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/02_LATMOS-Accounts.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/02_LATMOS-Accounts.t	(revision 2119)
@@ -0,0 +1,16 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl LATMOS-Accounts.t'
+
+#########################
+
+# change 'tests => 1' to 'tests => last_test_to_print';
+
+use Test::More tests => 2;
+BEGIN { use_ok('LATMOS::Accounts') };
+BEGIN { use_ok('LATMOS::Accounts::Mail') };
+
+#########################
+
+# Insert your test code below, the Test::More module is use()ed here so read
+# its man page ( perldoc Test::More ) for help writing this test script.
+
Index: tags/5.2.23/LATMOS-Accounts/t/05_utils.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/05_utils.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/05_utils.t	(revision 2119)
@@ -0,0 +1,114 @@
+use strict;
+use warnings;
+use Test::More tests => 46;
+use File::Temp qw(mkstemp);
+
+use_ok('LATMOS::Accounts::Utils');
+use_ok('LATMOS::Accounts::Mail');
+use LATMOS::Accounts::Log;
+
+ok(la_log(LA_NOTICE, "a notice"), "can run la_log");
+
+{
+my ($fh, $file) = mkstemp( "tmpfileXXXXX" );
+print $fh <<EOF;
+attr1: attr1
+attr: val1
+attr: val2
+EOF
+
+seek($fh, 0, 0);
+
+my %attributes = LATMOS::Accounts::Utils::parse_obj_file($fh);
+$fh = undef;
+unlink($file);
+
+# now testing
+is($attributes{'attr1'}, 'attr1', "can get attribute from file");
+ok(eq_set($attributes{'attr'}, [ qw(val1 val2) ]),
+        "can get multiple values attribute");
+
+$ENV{EDITOR} = 'sleep 1; /bin/touch';
+
+ok(LATMOS::Accounts::Utils::dump_read_temp_file(
+        sub { 1; }, sub { 1; }), "Can edit temp file");
+
+}
+{
+my ($fh, $file) = mkstemp( "tmpfileXXXXX" );
+binmode($fh, ":encoding(UTF-8)");
+print $fh <<EOF;
+sn,givenName
+Nom,PrÃ©nom
+EOF
+
+seek($fh, 0, 0);
+
+my $res = LATMOS::Accounts::Utils::loadCSV($fh);
+$fh = undef;
+unlink($file);
+
+# now testing
+is(scalar(@{ $res }), 1, "loadCSV return same number result");
+is($res->[0]{givenName}, "PrÃ©nom", "Attribute givenName is properly return");
+
+}
+
+ok(!LATMOS::Accounts::Utils::check_oid_validity('toto'), '"toto" is valid oid');
+ok(!LATMOS::Accounts::Utils::check_oid_validity('toTo'), '"toTo" is valid oid');
+ok(LATMOS::Accounts::Utils::check_oid_validity('to to'), '"to to" is invalid oid');
+ok(LATMOS::Accounts::Utils::check_oid_validity(' toto'), '" toto" is invalid oid');
+
+ok(!LATMOS::Accounts::Utils::check_ug_validity('toto'), '"toto" is valid username');
+ok(LATMOS::Accounts::Utils::check_ug_validity('toTo'), '"toTo" is invalid username');
+ok(LATMOS::Accounts::Utils::check_ug_validity('to to'), '"to to" is invalid username');
+ok(LATMOS::Accounts::Utils::check_ug_validity(' toto'), '" toto" is invalid username');
+ok(LATMOS::Accounts::Utils::check_ug_validity('5toto'), '"5toto" is invalid username');
+ok(!LATMOS::Accounts::Utils::check_ug_validity('t5oto'), '"t5oto" is valid username');
+
+is(LATMOS::Accounts::Utils::buildLogin('Toto'), 'toto', "buildLogin return toto");
+is(LATMOS::Accounts::Utils::buildLogin('To\'to'), 'toto', "buildLogin return toto");
+is(LATMOS::Accounts::Utils::buildLogin('#'), undef, "Impossible name return undef");
+is(LATMOS::Accounts::Utils::buildLogin('TotoTataTiti'), 'tototata', "buildLogin return 8 byte length login");
+is(LATMOS::Accounts::Utils::buildLogin(sub { $_[0] =~ /^toto$/ ? 0 : 1 }, 'toto'), undef, "buildLogin: cb works");
+is(LATMOS::Accounts::Utils::buildLogin(sub { $_[0] =~ /^toto$/ ? 0 : 1 }, 'Toto', 'Titi'), 'totot', "buildLogin: try others login");
+is(LATMOS::Accounts::Utils::buildLogin(sub { $_[0] =~ /^totot?$/ ? 0 : 1 }, 'toto', 'titi'), 'tototi', "buildLogin: try others login");
+is(LATMOS::Accounts::Utils::buildLogin(sub { $_[0] =~ /^toto/ ? 0 : 1 }, 'toto', 'titi'), undef, "buildLogin: return undef if no possibility");
+is(LATMOS::Accounts::Utils::buildLogin(sub { length($_[0]) > 9 }, 'TotoTataTiti'), 'tototatati', "buildLogin return 8 byte length login");
+
+ok(LATMOS::Accounts::Utils::genpassword(), "can generate a password");
+ok(LATMOS::Accounts::Utils::genpassword(
+        checkpassword => sub { 1 },
+    ), "can generate a password");
+
+my $clearpass = 'passwd';
+my $password = '';
+$password = LATMOS::Accounts::Utils::Crypt($clearpass, 'DES');
+like($password, qr/^\w\w.*/, 'Password encrypted using DES');
+is(crypt($clearpass, $password), $password);
+
+$password = LATMOS::Accounts::Utils::Crypt($clearpass, 'MD5');
+like($password, qr/^\$1\$.*/, 'Password encrypted using MD5');
+is(crypt($clearpass, $password), $password);
+
+$password = LATMOS::Accounts::Utils::Crypt($clearpass, 'sha-256');
+like($password, qr/^\$5\$.*/, 'Password encrypted using SHA-256');
+is(crypt($clearpass, $password), $password);
+
+$password = LATMOS::Accounts::Utils::Crypt($clearpass, 'SHA-512');
+like($password, qr/^\$6\$.*/, 'Password encrypted using SHA-512');
+is(crypt($clearpass, $password), $password);
+
+$password = LATMOS::Accounts::Utils::Crypt($clearpass, '5');
+like($password, qr/^\$5\$.*/, 'Password encrypted using SHA-256 (number)');
+is(crypt($clearpass, $password), $password);
+
+$password = LATMOS::Accounts::Utils::Crypt($clearpass);
+like($password, qr/^\$1\$.*/, 'Password encrypted using MD5 (default)');
+is(crypt($clearpass, $password), $password);
+
+is(yesno('yes'),  1, 'yes is true');
+is(yesno('true'), 1, 'true is true');
+is(yesno('23'),   1, '23 is true');
+is(yesno('no'),   0, 'no is false');
+is(yesno('what'), 'what', 'what is what?');
Index: tags/5.2.23/LATMOS-Accounts/t/06_cli.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/06_cli.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/06_cli.t	(revision 2119)
@@ -0,0 +1,5 @@
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+use_ok('LATMOS::Accounts::Cli');
Index: tags/5.2.23/LATMOS-Accounts/t/10_bases.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/10_bases.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/10_bases.t	(revision 2119)
@@ -0,0 +1,26 @@
+use strict;
+use warnings;
+use Test::More tests => 15;
+
+use_ok('LATMOS::Accounts::Bases');
+use_ok('LATMOS::Accounts::Bases::Objects');
+use_ok('LATMOS::Accounts::Bases::Dummy');
+use_ok('LATMOS::Accounts::Bases::Dummy::User');
+
+ok(my $dummyb = LATMOS::Accounts::Bases->new('dummy'), "Can get dummy base");
+isa_ok($dummyb, 'LATMOS::Accounts::Bases');
+isa_ok($dummyb, 'LATMOS::Accounts::Bases::Dummy');
+
+ok(my $user = $dummyb->get_object('user', 'foo'), "Can get user object");
+isa_ok($user, 'LATMOS::Accounts::Bases::Objects');
+isa_ok($user, 'LATMOS::Accounts::Bases::Dummy::User');
+
+ok(eq_set(
+    [ $dummyb->list_canonical_fields('user') ],
+    [qw(username homeDirectory) ],
+), "Can get canonicals fields");
+is( $dummyb->attribute('user', 'homeDirectory')->iname, "home", "can call get_fields_name");
+ok(!$dummyb->is_transactionnal, 'Dummy driver is not transactionnal');
+
+ok($dummyb->create_c_object('user', 'toto'), "Can create object");
+ok(!$dummyb->create_c_object('user', '1toto'), "Can't create object with wrong id");
Index: tags/5.2.23/LATMOS-Accounts/t/11_bases_unix.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/11_bases_unix.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/11_bases_unix.t	(revision 2119)
@@ -0,0 +1,76 @@
+use strict;
+use warnings;
+use Test::More tests => 33;
+use File::Temp;
+
+use_ok('LATMOS::Accounts::Bases');
+use_ok('LATMOS::Accounts::Bases::Objects');
+use_ok('LATMOS::Accounts::Bases::Unix');
+use_ok('LATMOS::Accounts::Bases::Unix::User');
+use_ok('LATMOS::Accounts::Bases::Unix::Group');
+
+my $dir = File::Temp::tempdir;
+
+foreach (qw(passwd shadow group gshadow)) {
+    system('cp', "testdata/$_", "$dir/$_");
+}
+
+ok(my $unixb = LATMOS::Accounts::Bases->new(
+    'unix',
+    { params => {
+        passwd  => "$dir/passwd",
+        shadow  => "$dir/shadow",
+        group   => "$dir/group",
+        gshadow => "$dir/gshadow",
+    } }
+    ), "Can get unix base");
+isa_ok($unixb, 'LATMOS::Accounts::Bases');
+isa_ok($unixb, 'LATMOS::Accounts::Bases::Unix');
+ok($unixb->load,   "Can load unix base");
+
+is( $unixb->attribute('user', 'homeDirectory')->iname, "home", "can call get_fields_name");
+
+ok(my $user =  $unixb->get_object('user', 'root'),  "Can get root user");
+ok(my $group = $unixb->get_object('group', 'root'), "Can get root group");
+
+is($user->get_c_field('homeDirectory'), '/root', 'Can get homeDirectory directory');
+is($user->get_field('home'), '/root', 'Can get home directory');
+
+is($user->get_field('shell'), '/bin/bash', 'Can get login shell');
+ok($user->set_fields(shell => '/bin/tcsh'), "Can set login shell");
+is($user->get_field('shell'), '/bin/tcsh', 'Can get modified login shell');
+ok($unixb->create_object('user', 'admin',
+    uid => 2, gid => 2,
+    home => '/home/admin',
+    shell => '/bin/sh',
+), "Can create a user");
+
+ok($unixb->commit, "Can save unix base");
+ok(eq_set($group->get_c_field('memberUID'), [ 'user' ]), 'can get group\'s member');
+ok($group->set_c_fields('memberUID', [ 'admin' ]), 'can set group\'s member');
+ok(eq_set($group->get_c_field('memberUID'), [ 'admin' ]), 'can get group\'s member after change');
+
+ok($group->addAttributeValue('memberUID', 'root'), 'can use addAttributeValue');
+ok(eq_set($group->get_c_field('memberUID'), [ 'admin', 'root' ]), 'can get group\'s member after addAttributeValue');
+ok($group->delAttributeValue('memberUID', 'root'), 'can use addAttributeValue');
+ok(eq_set($group->get_c_field('memberUID'), [ 'admin' ]), 'can get group\'s member after addAttributeValue');
+
+ok(my $unixbmod = LATMOS::Accounts::Bases->new(
+    'unix',
+    { params => { 
+    passwd  => "$dir/passwd",
+    shadow  => "$dir/shadow",
+    group   => "$dir/group",
+    gshadow => "$dir/gshadow",
+    } }
+    ), "Can get unix base");
+ok($unixbmod->load, "Can load saved db");
+ok($user = $unixbmod->get_object('user', 'root'),
+    "Can get object from modified db");
+is($user->get_field('shell'), '/bin/tcsh', 'Can get modified login shell');
+
+ok($unixbmod->rename_object('user', 'root', 'rootb'), "can rename object");
+ok($unixbmod->get_object('user', 'rootb'), "can get renamed object");
+ok(!$unixbmod->get_object('user', 'root'), "can get old object");
+
+system('rm', '-fr', $dir);
Index: tags/5.2.23/LATMOS-Accounts/t/12_bases_sql.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/12_bases_sql.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/12_bases_sql.t	(revision 2119)
@@ -0,0 +1,14 @@
+use strict;
+use warnings;
+use Test::More tests => 10;
+
+use_ok('LATMOS::Accounts::Bases');
+use_ok('LATMOS::Accounts::Bases::Objects');
+use_ok('LATMOS::Accounts::Bases::Sql');
+use_ok('LATMOS::Accounts::Bases::Sql::User');
+use_ok('LATMOS::Accounts::Bases::Sql::Group');
+use_ok('LATMOS::Accounts::Bases::Sql::Site');
+use_ok('LATMOS::Accounts::Bases::Sql::Aliases');
+use_ok('LATMOS::Accounts::Bases::Sql::Revaliases');
+use_ok('LATMOS::Accounts::Bases::Sql::Address');
+use_ok('LATMOS::Accounts::Bases::Sql::Onlyaddress');
Index: tags/5.2.23/LATMOS-Accounts/t/13_bases_ldap.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/13_bases_ldap.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/13_bases_ldap.t	(revision 2119)
@@ -0,0 +1,11 @@
+use strict;
+use warnings;
+use Test::More tests => 6;
+
+use_ok('LATMOS::Accounts::Bases');
+use_ok('LATMOS::Accounts::Bases::Objects');
+use_ok('LATMOS::Accounts::Bases::Ldap');
+use_ok('LATMOS::Accounts::Bases::Ldap::User');
+use_ok('LATMOS::Accounts::Bases::Ldap::Group');
+use_ok('LATMOS::Accounts::Bases::Ldap::Onlyaddress');
+
Index: tags/5.2.23/LATMOS-Accounts/t/14_bases_ad.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/14_bases_ad.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/14_bases_ad.t	(revision 2119)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+use_ok('LATMOS::Accounts::Bases');
+use_ok('LATMOS::Accounts::Bases::Objects');
+use_ok('LATMOS::Accounts::Bases::Ad');
+use_ok('LATMOS::Accounts::Bases::Ad::User');
+use_ok('LATMOS::Accounts::Bases::Ad::Group');
+
Index: tags/5.2.23/LATMOS-Accounts/t/15_bases_mail.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/15_bases_mail.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/15_bases_mail.t	(revision 2119)
@@ -0,0 +1,53 @@
+use strict;
+use warnings;
+use Test::More tests => 16;
+use File::Temp;
+
+use_ok('LATMOS::Accounts::Bases');
+use_ok('LATMOS::Accounts::Bases::Objects');
+use_ok('LATMOS::Accounts::Bases::Mail');
+use_ok('LATMOS::Accounts::Bases::Mail::Aliases');
+use_ok('LATMOS::Accounts::Bases::Mail::Revaliases');
+
+my $dir = File::Temp::tempdir;
+
+foreach (qw(aliases revaliases)) {
+    system('cp', "testdata/$_", "$dir/$_");
+}
+
+ok(my $mailb = LATMOS::Accounts::Bases->new(
+    'mail',
+    {
+    params => {
+        aliases  => "$dir/aliases",
+        revaliases  => "$dir/revaliases",
+    }}
+    ), "Can get mail base");
+
+isa_ok($mailb, 'LATMOS::Accounts::Bases');
+isa_ok($mailb, 'LATMOS::Accounts::Bases::Mail');
+ok($mailb->load,   "Can load mail base");
+
+is( $mailb->attribute('aliases', 'forward')->iname, "forward", "can call get_field_name");
+
+ok(my $alias =  $mailb->get_object('aliases', 'name'),  "Can get alias name");
+ok(my $revaliases = $mailb->get_object('revaliases', 'name'), "Can get revalias name");
+
+my ($to) = $alias->get_attributes('forward');
+is($to, 'to', 'Can get aliases');
+
+ok($mailb->create_object('aliases', 'admin',
+        forward => 'root'
+), "Can create an aliases");
+
+ok($mailb->commit, "Can save mail base");
+
+ok(my $mailbmod = LATMOS::Accounts::Bases->new(
+    'mail',
+    { params => {
+    aliases  => "$dir/aliases",
+    revaliases  => "$dir/revaliases",
+    }}
+    ), "Can get mail base");
+
+system('rm', '-fr', $dir);
Index: tags/5.2.23/LATMOS-Accounts/t/16_bases_heimdal.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/16_bases_heimdal.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/16_bases_heimdal.t	(revision 2119)
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use Test::More;
+
+eval "use Heimdal::Kadm5;";
+if ($@) {
+    plan skip_all => "Heimdal::Kadm5 is not present";
+} else {
+    plan tests => 4;
+}
+
+use_ok('LATMOS::Accounts::Bases');
+use_ok('LATMOS::Accounts::Bases::Objects');
+use_ok('LATMOS::Accounts::Bases::Heimdal');
+use_ok('LATMOS::Accounts::Bases::Heimdal::User');
+
Index: tags/5.2.23/LATMOS-Accounts/t/20_accounts.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/20_accounts.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/20_accounts.t	(revision 2119)
@@ -0,0 +1,14 @@
+use strict;
+use warnings;
+use Test::More tests => 4;
+use File::Temp;
+
+use_ok('LATMOS::Accounts');
+use_ok('LATMOS::Accounts::Maintenance');
+
+isa_ok(
+    my $accounts = LATMOS::Accounts->new('testdata/configdir'),
+    'LATMOS::Accounts'
+);
+
+ok($accounts->base('unix'), "can load a config'ed base from cache");
Index: tags/5.2.23/LATMOS-Accounts/t/21_acls.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/21_acls.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/21_acls.t	(revision 2119)
@@ -0,0 +1,66 @@
+use strict;
+use warnings;
+use Test::More tests => 15;
+
+use_ok('LATMOS::Accounts::Acls');
+use_ok('LATMOS::Accounts::Acls::Acl');
+
+{
+    ok(my $acl = LATMOS::Accounts::Acls::Acl->new('user.uid', [ 'user1: read' ]),
+        "can create single acl");
+    is($acl->match(fakeobject->new('user', 'user1'), 'uid', 'r', 'user1'), 1, "acl match");
+    is($acl->match(fakeobject->new('user', 'user2'), 'uid', 'w', 'user1') || 0, 0, "acl match");
+}
+
+{
+    ok(my $acls = LATMOS::Accounts::Acls->new, "Can create new acls objects");
+    ok($acls->add('user.uid', [ 'user1: read' ]), "Can add new acl");
+}
+
+{
+    ok(my $acls = LATMOS::Accounts::Acls->new('testdata/acls1'),
+        "Can create new acls objects from file");
+    # now testing...
+    is(
+        $acls->check(fakeobject->new('user', 'user1'), 'uid', 'r', 'user1'),
+        1, "user can read uid");
+    is(
+        $acls->check(fakeobject->new('user', 'user1'), 'userPassword', 'r', 'user1'),
+        0, "user cannot read userPassword");
+    is(
+        $acls->check(fakeobject->new('user', 'user1'), 'uid', 'w', 'user1'),
+        0, "user cannot write uid");
+    is(
+        $acls->check(fakeobject->new('user', 'user1'), 'givenName', 'w', 'user1'),
+        1, "user can write givenName");
+    is(
+        $acls->check(fakeobject->new('user', 'user1'), 'uid', 'w',
+            'user1', [ 'admin' ]), 0, "user cannot write user attribute");
+    is(
+        $acls->check(fakeobject->new('group', 'group1'), 'uid', 'w',
+            'user1', [ 'admin' ]), 1, "user can write group attribute");
+    is(
+        $acls->check('user', 'CREATE', 'w', 'user1', [ 'admin' ]), 1,
+            "user can create a new user");
+}
+
+
+# A fake object to test only ACLs
+package fakeobject;
+
+sub new {
+    my ($class, $type, $id) = @_;
+    bless({type => $type, id => $id}, $class);
+}
+
+sub type {
+    $_[0]->{type}
+}
+
+sub id {
+    $_[0]->{id}
+}
+
+sub _get_c_field {
+    $_[0]->{id}
+}
Index: tags/5.2.23/LATMOS-Accounts/t/22_accounts_attributes.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/22_accounts_attributes.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/22_accounts_attributes.t	(revision 2119)
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use Test::More tests => 4;
+use File::Temp;
+
+use_ok('LATMOS::Accounts');
+use_ok('LATMOS::Accounts::Bases::Attributes');
+
+isa_ok(
+    my $accounts = LATMOS::Accounts->new('testdata/configdir'),
+    'LATMOS::Accounts'
+);
+
+ok(my $base = $accounts->base('unix'), "Can get base");
+
+my $attr = LATMOS::Accounts::Bases::Attributes->new('memberUID', $base, 'group');
Index: tags/5.2.23/LATMOS-Accounts/t/23_ochelper.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/23_ochelper.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/23_ochelper.t	(revision 2119)
@@ -0,0 +1,8 @@
+# $Id: 23_ochelper.t 2035 2010-04-17 12:56:26Z nanardon $
+
+use strict;
+use warnings;
+use Test::More tests => 2;
+
+use_ok('LATMOS::Accounts::Bases::OCHelper');
+use_ok('LATMOS::Accounts::Bases::OCHelper::User');
Index: tags/5.2.23/LATMOS-Accounts/t/25_la_synchro.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/25_la_synchro.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/25_la_synchro.t	(revision 2119)
@@ -0,0 +1,72 @@
+use strict;
+use warnings;
+use Test::More tests => 15;
+use File::Temp;
+use File::Path;
+use Config::IniFiles;
+
+use_ok('LATMOS::Accounts::Synchro');
+use_ok('LATMOS::Accounts');
+
+my $workdir = File::Temp::tempdir();
+
+my $config = Config::IniFiles->new();
+
+$config->AddSection('source');
+$config->newval('source', 'type', 'unix');
+$config->newval('source', 'use_shadow', 1);
+$config->AddSection('dest');
+$config->newval('dest', 'type', 'unix');
+$config->newval('dest', 'use_shadow', 1);
+
+foreach (qw(passwd shadow group gshadow)) {
+    system('cp', "testdata/synchro/dest/$_", "$workdir/$_");
+    $config->newval('source', $_, "testdata/synchro/source/$_");
+    $config->newval('dest', $_, "$workdir/$_");
+}
+
+ok($config->WriteConfig("$workdir/latmos-accounts.ini"), "can write config file for test");
+
+diag("Write test done in $workdir/");
+
+isa_ok(
+    my $accounts = LATMOS::Accounts->new($workdir),
+    'LATMOS::Accounts'
+);
+
+ok($accounts->base('source'), "can get source base");
+ok($accounts->base('dest'),   "can get dest   base");
+
+my $synchro = LATMOS::Accounts::Synchro->new(
+    $accounts->base('source'), $accounts->base('dest'),
+    state_file => "$workdir/status.ini",
+);
+
+isa_ok($synchro, 'LATMOS::Accounts::Synchro');
+
+ok($synchro->process, "process get succefull");
+
+ok(my $unixb = LATMOS::Accounts::Bases->new(
+        'unix',
+        {
+        params => {
+            passwd  => "$workdir/passwd",
+            shadow  => "$workdir/shadow",
+            group   => "$workdir/group",
+            gshadow => "$workdir/gshadow",
+        }
+        }
+    ), "Can get unix base");
+isa_ok($unixb, 'LATMOS::Accounts::Bases::Unix');
+ok($unixb->load,   "Can load unix base");
+
+{
+ok(my $user =  $unixb->get_object('user', 'bin'),  "Can get bin user");
+is($user->get_c_field('homeDirectory'), '/bin', 'Can get homeDirectory directory');
+}
+{
+ok(my $user =  $unixb->get_object('user', 'adm'),  "Can get adm user");
+is($user->get_field('gecos'), 'adm', 'Can get home directory');
+}
+
+rmtree($workdir);
Index: tags/5.2.23/LATMOS-Accounts/t/26_la_synchaccess.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/26_la_synchaccess.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/26_la_synchaccess.t	(revision 2119)
@@ -0,0 +1,56 @@
+use strict;
+use warnings;
+use Test::More tests => 8;
+use File::Temp;
+use File::Path;
+use Config::IniFiles;
+
+use_ok('LATMOS::Accounts::SynchAccess');
+use_ok('LATMOS::Accounts');
+
+my $workdir = File::Temp::tempdir();
+mkdir "$workdir/source";
+mkdir "$workdir/dest";
+
+my $config = Config::IniFiles->new();
+
+$config->AddSection('source');
+$config->newval('source', 'type', 'unix');
+$config->newval('source', 'use_shadow', 1);
+$config->AddSection('dest');
+$config->newval('dest', 'type', 'unix');
+$config->newval('dest', 'use_shadow', 1);
+$config->newval('source', 'push', 'dest');
+
+$config->AddSection('_default_');
+$config->newval('_default_', 'sync', 'main');
+$config->AddSection('sync:main');
+$config->newval('sync:main', 'from', 'source');
+$config->newval('sync:main', 'to', 'dest');
+
+foreach my $dir (qw(source dest)) {
+    foreach (qw(passwd shadow group gshadow)) {
+        system('cp', "testdata/synchro/$dir/$_", "$workdir/$dir/$_");
+        $config->newval($dir, $_, "testdata/synchro/$dir/$_");
+    }
+}
+
+ok($config->WriteConfig("$workdir/latmos-accounts.ini"), "can write config file for test");
+
+diag("Write test done in $workdir/");
+
+isa_ok(
+    my $accounts = LATMOS::Accounts->new($workdir),
+    'LATMOS::Accounts'
+);
+
+ok($accounts->base('source'), "can get source base");
+ok($accounts->base('dest'),   "can get dest   base");
+
+isa_ok(my $syncaccess = $accounts->sync_access, 'LATMOS::Accounts::SynchAccess');
+isa_ok($syncaccess->get_object('user', 'root'), 'LATMOS::Accounts::SynchAccess::Objects');
+
+$syncaccess->type;
+$syncaccess->label;
+
+rmtree($workdir);
Index: tags/5.2.23/LATMOS-Accounts/t/30_la_task.t
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/30_la_task.t	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/30_la_task.t	(revision 2119)
@@ -0,0 +1,7 @@
+use strict;
+use warnings;
+use Test::More tests => 2;
+
+use_ok('LATMOS::Accounts::Task');
+
+ok(LATMOS::Accounts::Task->new('dummy'), "Can load dummy task");
Index: tags/5.2.23/LATMOS-Accounts/t/scripts.pl
===================================================================
--- tags/5.2.23/LATMOS-Accounts/t/scripts.pl	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/t/scripts.pl	(revision 2119)
@@ -0,0 +1,12 @@
+use strict;
+use warnings;
+use Test::More;
+
+eval "use Test::Script";
+plan skip_all => 'Test::Script required' if $@;
+
+plan tests => scalar(@ARGV);
+
+foreach(@ARGV) {
+    script_compiles($_);
+}
Index: tags/5.2.23/LATMOS-Accounts/templates/mail/account_expire.mail
===================================================================
--- tags/5.2.23/LATMOS-Accounts/templates/mail/account_expire.mail	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/templates/mail/account_expire.mail	(revision 2119)
@@ -0,0 +1,43 @@
+(English version follows)
+
+Bonjour [% obj.get_c_field('displayName') %],
+
+sauf erreur ou ommission de notre part votre compte [% obj.id %] expire
+dans [% IF days %][% days %] jours[% ELSE %]aujourd'hui[% END %] ([% obj.get_c_field('expire') %]).
+En aucun cas vos fichiers ne seront dÃ©truits. 
+
+Si ce dernier devait Ãªtre prolongÃ©, merci de dire Ã  votre responsable
+de contacter svp@latmos.ipsl.fr afin de nous donner la nouvelle date.
+
+Votre statut:
+
+SociÃ©tÃ©: [% obj.get_c_field('company') %]
+
+DÃ©partement: [% obj.get_c_field('department') %]
+
+Contrat: [% obj.get_c_field('contratType') %]
+
+Cordialement.
+L'Ã©quipe d'administration.
+
+===
+
+Hello [% obj.get_c_field('displayName') %],
+
+Except error or omission on our part, your account login expires
+in [% IF days %][% days %] days[% ELSE %]today[% END %] ([% obj.get_c_field('expire') %]).
+In no case your files will be destroyed.
+
+If your account had to be extended, please ask your manager to contact
+svp@latmos.ipsl.fr to give us a new expiration date.
+
+Your status:
+
+Company: [% obj.get_c_field('company') %]
+
+Department: [% obj.get_c_field('department') %]
+
+Contract: [% obj.get_c_field('contratType') %]
+
+Best regards.
+The administration team.
Index: tags/5.2.23/LATMOS-Accounts/templates/mail/account_expired_reminder.mail
===================================================================
--- tags/5.2.23/LATMOS-Accounts/templates/mail/account_expired_reminder.mail	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/templates/mail/account_expired_reminder.mail	(revision 2119)
@@ -0,0 +1,13 @@
+Bonjour [% manager.get_attributes('displayName') %],
+
+Les comptes des personnes suivantes ont expirÃ©s. Sans autre indication de votre
+part ils seront dÃ©finitivement supprimÃ©s (les donnÃ©es associÃ©es ne seront pas
+impactÃ©es).
+
+Merci de nous prÃ©venir rapidement si ceux-ci Ã©taient encore necessaires.
+
+[% FOREACH user = users %]
+[% user.id %] - [% user.get_attributes('displayName') %] ([% user.get_attributes('expireText') %])
+[% END %]
+
+Cordialement.
Index: tags/5.2.23/LATMOS-Accounts/templates/mail/passwordreset.mail
===================================================================
--- tags/5.2.23/LATMOS-Accounts/templates/mail/passwordreset.mail	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/templates/mail/passwordreset.mail	(revision 2119)
@@ -0,0 +1,23 @@
+(English version follows)
+
+Bonjour [% obj.get_c_field('displayName') %],
+
+Quelqu'un, nous espÃ©rons vous, a demandÃ© un remise Ã  zÃ©ro de votre mot de passe.
+
+Pour changer votre mot de passe rendez-vous ici:
+[% url %] 
+
+Cordialement.
+L'Ã©quipe d'administration.
+
+===
+
+Hello [% obj.get_c_field('displayName') %],
+
+Someone, hopefully you, request a password reset.
+
+To do so please follow this link:
+[% url %]
+
+Best regards.
+The administration team.
Index: tags/5.2.23/LATMOS-Accounts/testdata/acls1
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/acls1	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/acls1	(revision 2119)
@@ -0,0 +1,18 @@
+# $Id$
+
+*.userPassword
+    *: read,deny # the deny will mask the read
+
+user.CREATE
+    user1: write
+
+user.{sn,\
+    givenName} # user can change their name
+    $cn: write,read
+    %tgroup: read
+
+group.*
+    %admin: read, write
+
+*.*
+    *:read
Index: tags/5.2.23/LATMOS-Accounts/testdata/acls2
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/acls2	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/acls2	(revision 2119)
@@ -0,0 +1,2 @@
+*.*
+    *: read,write
Index: tags/5.2.23/LATMOS-Accounts/testdata/aliases
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/aliases	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/aliases	(revision 2119)
@@ -0,0 +1,2 @@
+name:to
+firstname.lastname:to
Index: tags/5.2.23/LATMOS-Accounts/testdata/config
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/config	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/config	(revision 2119)
@@ -0,0 +1,10 @@
+[_default_]
+acls = testdata/acls2
+
+[unix]
+type=unix
+passwd = testdata/passwd
+shadow = testdata/shadow
+group = testdata/group
+gshadow = testdata/gshadow
+use_shadow = 1
Index: tags/5.2.23/LATMOS-Accounts/testdata/configdir/latmos-accounts.ini
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/configdir/latmos-accounts.ini	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/configdir/latmos-accounts.ini	(revision 2119)
@@ -0,0 +1,10 @@
+[_default_]
+acls = testdata/acls2
+
+[unix]
+type=unix
+passwd = testdata/passwd
+shadow = testdata/shadow
+group = testdata/group
+gshadow = testdata/gshadow
+use_shadow = 1
Index: tags/5.2.23/LATMOS-Accounts/testdata/group
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/group	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/group	(revision 2119)
@@ -0,0 +1,1 @@
+root:x:0:user
Index: tags/5.2.23/LATMOS-Accounts/testdata/gshadow
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/gshadow	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/gshadow	(revision 2119)
@@ -0,0 +1,1 @@
+root:::
Index: tags/5.2.23/LATMOS-Accounts/testdata/passwd
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/passwd	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/passwd	(revision 2119)
@@ -0,0 +1,1 @@
+root:x:0:0:root:/root:/bin/bash
Index: tags/5.2.23/LATMOS-Accounts/testdata/revaliases
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/revaliases	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/revaliases	(revision 2119)
@@ -0,0 +1,1 @@
+name:Firstname.Lastname
Index: tags/5.2.23/LATMOS-Accounts/testdata/shadow
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/shadow	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/shadow	(revision 2119)
@@ -0,0 +1,1 @@
+root:$2a$08$MqAzx2lrcMdzykdNPsNH0erE3kUOxEfeMqOtsfbyDdQDBWOTJl/L6:14302:0:99999:7:::
Index: tags/5.2.23/LATMOS-Accounts/testdata/synchro/dest/group
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/synchro/dest/group	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/synchro/dest/group	(revision 2119)
@@ -0,0 +1,2 @@
+root:x:0:
+test:x:4:
Index: tags/5.2.23/LATMOS-Accounts/testdata/synchro/dest/gshadow
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/synchro/dest/gshadow	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/synchro/dest/gshadow	(revision 2119)
@@ -0,0 +1,1 @@
+root:::
Index: tags/5.2.23/LATMOS-Accounts/testdata/synchro/dest/passwd
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/synchro/dest/passwd	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/synchro/dest/passwd	(revision 2119)
@@ -0,0 +1,2 @@
+root:x:0:0:root:/root:/bin/bash
+adm:x:3:4:admin:/var/adm:/bin/zsh
Index: tags/5.2.23/LATMOS-Accounts/testdata/synchro/dest/shadow
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/synchro/dest/shadow	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/synchro/dest/shadow	(revision 2119)
@@ -0,0 +1,1 @@
+root:$2a$08$MqAzx2lrcMdzykdNPsNH0erE3kUOxEfeMqOtsfbyDdQDBWOTJl/L6:14302:0:99999:7:::
Index: tags/5.2.23/LATMOS-Accounts/testdata/synchro/source/group
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/synchro/source/group	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/synchro/source/group	(revision 2119)
@@ -0,0 +1,5 @@
+root:x:0:
+test1:x:1:
+test2:x:2:
+test3:x:3:
+test4:x:4:
Index: tags/5.2.23/LATMOS-Accounts/testdata/synchro/source/gshadow
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/synchro/source/gshadow	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/synchro/source/gshadow	(revision 2119)
@@ -0,0 +1,1 @@
+root:::
Index: tags/5.2.23/LATMOS-Accounts/testdata/synchro/source/passwd
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/synchro/source/passwd	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/synchro/source/passwd	(revision 2119)
@@ -0,0 +1,4 @@
+root:x:0:0:root:/root:/bin/bash
+bin:x:1:1:bin:/bin:/bin/sh
+daemon:x:2:2:daemon:/sbin:/bin/sh
+adm:x:3:4:adm:/var/adm:/bin/sh
Index: tags/5.2.23/LATMOS-Accounts/testdata/synchro/source/shadow
===================================================================
--- tags/5.2.23/LATMOS-Accounts/testdata/synchro/source/shadow	(revision 2119)
+++ tags/5.2.23/LATMOS-Accounts/testdata/synchro/source/shadow	(revision 2119)
@@ -0,0 +1,1 @@
+root:$2a$08$MqAzx2lrcMdzykdNPsNH0erE3kUOxEfeMqOtsfbyDdQDBWOTJl/L6:14302:0:99999:7:::
Index: tags/5.2.23/annouceRelease
===================================================================
--- tags/5.2.23/annouceRelease	(revision 2119)
+++ tags/5.2.23/annouceRelease	(revision 2119)
@@ -0,0 +1,78 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Mail::Sendmail;
+
+use lib qw(LATMOS-Accounts/lib);
+
+use LATMOS::Accounts;
+
+my $version = $LATMOS::Accounts::VERSION;
+my $dest = 'link-accounts-discuss@forge.ipsl.jussieu.fr';
+
+my $subject = "New Link::Accounts release: $version";
+
+my $message = <<EOF;
+
+A new version of Link::Account has been released.
+
+You can download it here:
+
+http://forge.ipsl.jussieu.fr/link-accounts/web/release/$version
+
+http://forge.ipsl.jussieu.fr/link-accounts/web/release/$version/latmos-accounts-$version-1mdv2010.0.noarch.rpm
+http://forge.ipsl.jussieu.fr/link-accounts/web/release/$version/latmos-accounts-web-$version-1mdv2010.0.noarch.rpm
+
+Regards.
+
+EOF
+
+sub parse_changes {
+    my ($files) = @_;
+
+    my $text = '';
+
+    if (open(my $handle, '<', $files)) {
+        while(my $line = <$handle>) {
+            if ($line =~ /^$version\s*$/) {
+                while ($line = <$handle>) {
+                    $line =~ /^\S/ and last;
+                    $text .= $line;
+                }
+                last;
+            }
+        }
+        close($handle);
+    }
+
+    return $text
+}
+
+if (my $changes = parse_changes('LATMOS-Accounts/Changes')) {
+    if ($changes) {
+        $message .= "\nChanges for Link::Accounts\n";
+        $message .= $changes;
+    } else {
+        $message .= "\nNo changes reported for Link::Accounts\n";
+    }
+}
+if (my $changes = parse_changes('LATMOS-Accounts-Web/Changes')) {
+    if ($changes) {
+        $message .= "\nChanges for Link::Accounts Web Application\n";
+        $message .= $changes;
+    } else {
+        $message .= "\nNo changes reported for Link::Accounts Web application\n";
+    }
+}
+
+if (!Mail::Sendmail::sendmail(
+    To => $dest,
+    Subject => $subject,
+    Message => $message,
+    From => 'link-accounts-devel@forge.ipsl.jussieu.fr',
+    smtp => 'mailhost',
+)) {
+    die "Cannot send mail: " . $Mail::Sendmail::error . "\n";
+}
Index: tags/5.2.23/latmosnextversion
===================================================================
--- tags/5.2.23/latmosnextversion	(revision 2119)
+++ tags/5.2.23/latmosnextversion	(revision 2119)
@@ -0,0 +1,44 @@
+#!/bin/sh -e
+
+VERSION=$1
+
+if [ -z "$VERSION" ]; then
+   echo "No version given"
+   exit 1
+fi
+
+svn update
+
+perl -pi -e "s/our \\\$VERSION =.*/our \\\$VERSION = '$VERSION';/" LATMOS-Accounts/lib/LATMOS/Accounts.pm
+perl -pi -e "s/our \\\$VERSION =.*/our \\\$VERSION = '$VERSION';/" LATMOS-Accounts-Web/lib/LATMOS/Accounts/Web.pm
+
+perl -pi -e "s/^XXX/$VERSION/m" LATMOS-Accounts/Changes
+perl -pi -e "s/^XXX/$VERSION/m" LATMOS-Accounts-Web/Changes
+
+find -name "*.tar.gz" -o -name "*.rpm" | xargs rm
+
+(
+    cd LATMOS-Accounts
+    perl Makefile.PL
+    make rpm
+)
+(
+    cd LATMOS-Accounts-Web/;
+    perl Makefile.PL;
+    make rpm
+)
+
+(
+    cd ..
+    svn cp trunk tags/$VERSION
+)
+
+rsync -avPH ./LATMOS-Accounts/noarch/latmos-accounts-*-*.noarch.rpm ./LATMOS-Accounts-Web/noarch/latmos-accounts-web-*-*.noarch.rpm root@yuko.aero.jussieu.fr:/tmp
+rsync -avPH ./LATMOS-Accounts/noarch/latmos-accounts-*-*.noarch.rpm ./LATMOS-Accounts-Web/noarch/latmos-accounts-web-*-*.noarch.rpm root@kohane.aero.jussieu.fr:/tmp
+rsync -avPH ./LATMOS-Accounts/noarch/latmos-accounts-*-*.noarch.rpm \
+            ./LATMOS-Accounts-Web/noarch/latmos-accounts-web-*-*.noarch.rpm \
+            ./LATMOS-Accounts/*.src.rpm \
+            ./LATMOS-Accounts-Web/*.src.rpm \
+            nanardon@forge.ipsl.jussieu.fr:/ipsl/forge/projets/link-accounts/web/release/${VERSION}/
+ssh root@kohane.aero.jussieu.fr rpm -Fvh /tmp/*.rpm
+
