+
+
+
+
+
+
+
diff --git a/admin/config.php b/admin/config.php
new file mode 100644
index 0000000..40fd2bf
--- /dev/null
+++ b/admin/config.php
@@ -0,0 +1,157 @@
+prepare($sql);
+// On regarde le nombre de résolution d'ago qui sera important par la suite pour créer des variables de sessions et le formulaire de vote
+$option = 'resolution_ago_nb';
+$stmt->bindParam(':option', $option, PDO::PARAM_STR);
+$stmt->execute();
+$results = $stmt->fetch();
+$resolution_ago_nb = $results['option_value'];
+// On regarde le nombre de résolution d'age qui sera important par la suite pour créer des variables de sessions et le formulaire de vote
+$option = 'resolution_age_nb';
+$stmt->bindParam(':option', $option, PDO::PARAM_STR);
+$stmt->execute();
+$results = $stmt->fetch();
+$resolution_age_nb = $results['option_value'];
+// On regarde combien de postes sont à pourvoir
+$option = 'cs_nb_poste';
+$stmt->bindParam(':option', $option, PDO::PARAM_STR);
+$stmt->execute();
+$results = $stmt->fetch();
+$cs_nb_poste = $results['option_value'];
+// On va chercehr l'url des documents relatifs à l'AG
+$option = 'url_docs';
+$stmt->bindParam(':option', $option, PDO::PARAM_STR);
+$stmt->execute();
+$results = $stmt->fetch();
+$url_docs = $results['option_value'];
+?>
+
+
+
+
+
+
+
+
+
+
+
Vous êtes authentifié sous l'utilisateur :
+
+ Se déconnecter
+
+
+
Configuration générale
+
+
+
+
Les résolutions d'AGO
+
+
+
+
+
+
+
Les résolutions d'AGE
+
+
+
+
+
+
+
L'élection des membres du CS
+
Idéalement, il faut entrer les noms des candidats dans l'ordre alphabétique.
+
Pour supprimer un nom de la liste, il suffit de laisser le champ vide, il sera automatiquement supprimé lors de l'enregistrement.
+
+
+
+
Divers
+
Ci-dessous la requête SQL pour obtenir la liste des coopérateurs :
+
SELECT reference, thirds.headquarterEmail, lastname, firstname
+ FROM clients
+ INNER JOIN thirds ON thirds.uid = clients.uid
+ INNER JOIN partsSociales ON partsSociales.uid = clients.uid
+ WHERE etat = "achetee"
+ GROUP BY clients.uid
+
Pensez à modifier les dates dans explication.php et merci.php.
+ Et également dans mail.inc.php.
+
+
+
+
+
+
diff --git a/admin/css/main.css b/admin/css/main.css
new file mode 100644
index 0000000..280a996
--- /dev/null
+++ b/admin/css/main.css
@@ -0,0 +1,573 @@
+/*! HTML5 Boilerplate v4.3.0 | MIT License | http://h5bp.com/ */
+
+/*
+ * What follows is the result of much research on cross-browser styling.
+ * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
+ * Kroc Camen, and the H5BP dev community and team.
+ */
+
+/* ==========================================================================
+ Base styles: opinionated defaults
+ ========================================================================== */
+
+html,
+button,
+input,
+select,
+textarea {
+ color: #222;
+}
+
+html {
+ line-height: 1.4;
+ font: 0.9em/1.3em "Droid Sans",Helvetica,Arial,sans-serif;
+}
+a {color: black;}
+/*
+ * Remove text-shadow in selection highlight: h5bp.com/i
+ * These selection rule sets have to be separate.
+ * Customize the background color to match your design.
+ */
+
+::-moz-selection {
+ background: #b3d4fc;
+ text-shadow: none;
+}
+
+::selection {
+ background: #b3d4fc;
+ text-shadow: none;
+}
+
+/*
+ * A better looking default horizontal rule
+ */
+
+hr {
+ display: block;
+ height: 1px;
+ border: 0;
+ border-top: 1px solid #ccc;
+ margin: 1em 0;
+ padding: 0;
+}
+hr:last-child {
+ display: block;
+ height: 1px;
+ border: 0;
+ border-top: 4px solid #555;
+ margin: 1em 0;
+ padding: 0;
+}
+/*
+ * Remove the gap between images, videos, audio and canvas and the bottom of
+ * their containers: h5bp.com/i/440
+ */
+
+audio,
+canvas,
+img,
+video {
+ vertical-align: middle;
+}
+
+/*
+ * Remove default fieldset styles.
+ */
+
+fieldset {
+ border: 0;
+ margin: 0;
+ padding: 0;
+}
+
+/*
+ * Allow only vertical resizing of textareas.
+ */
+
+textarea {
+ resize: vertical;
+}
+
+/* ==========================================================================
+ Browse Happy prompt
+ ========================================================================== */
+
+.browsehappy {
+ margin: 0.2em 0;
+ background: #ccc;
+ color: #000;
+ padding: 0.2em 0;
+}
+
+/* ==========================================================================
+ Author's custom styles
+ ========================================================================== */
+#navigationMenu {
+ position: absolute;
+ top: 0;
+ left: -2em;
+}
+
+#navigationMenu li{
+ list-style:none;
+ height:25px;
+ padding:5px 2px;
+ width:25px;
+}
+
+#navigationMenu span{
+ width:0;
+ left:34px;
+ padding:0;
+ position:absolute;
+ overflow:hidden;
+ font-family:'Myriad Pro',Arial, Helvetica, sans-serif;
+ font-size:14px;
+ white-space:nowrap;
+ line-height:28px;
+ -webkit-transition: 0.25s;
+ -moz-transition: 0.25s;
+ transition: 0.25s;
+}
+
+#navigationMenu a{
+ /*background:url('../img/navigation.jpg') no-repeat;*/
+ height:25px;
+ width:25px;
+ display:block;
+ position:relative;
+ color: black;
+}
+
+#navigationMenu a:hover span, #navigationMenu a:hover span{ width:auto; padding:0 20px;overflow:visible; }
+#navigationMenu a:hover, #navigationMenu a:hover{
+ text-decoration:none;
+ -moz-box-shadow:0 0 5px #E2E2E2;
+ -webkit-box-shadow:0 0 5px #E2E2E2;
+ box-shadow:0 0 5px #E2E2E2;
+}
+#navigationMenu span{
+ background-color:#E2E2E2;
+ color:#004F9B;
+ font-weight: bold;
+}
+/*
+#navigationMenu .home { background-position: 0 0;}
+
+#navigationMenu .password { background-position: -150px -50px;}
+#navigationMenu .password:hover { background-position:-150px 0;}
+
+#navigationMenu .mail { background-position: -100px -50px;}
+#navigationMenu .mail:hover { background-position:-100px 0;}
+
+#navigationMenu .listecooperateurs { background-position: -200px -50px;}
+#navigationMenu .listecooperateurs:hover { background-position:-200px 0;}
+
+#navigationMenu .listenonvotants { background-position: -300px -50px;}
+#navigationMenu .listenonvotants:hover{ background-position:-300px 0;}
+
+#navigationMenu .listevotants { background-position: -250px -50px;}
+#navigationMenu .listevotants:hover { background-position:-250px 0;}
+
+#navigationMenu .resultats { background-position: -50px -50px;}
+#navigationMenu .resultats:hover { background-position: -50px 0;}
+
+#navigationMenu .config { background-position: -350px -50px;}
+#navigationMenu .config:hover { background-position: -350px 0;}
+*/
+.mt1 {margin-top:1em;}
+.mr1 {margin-right:1em;}
+.mb1 {margin-bottom:1em;}
+.ml1 {margin-left:1em;}
+.mt2 {margin-top:2em;}
+.mr2 {margin-right:2em;}
+.mb2 {margin-bottom:2em;}
+.ml2 {margin-left:2em;}
+.pt1 {padding-top:1em;}
+.pr1 {padding-right:1em;}
+.pb1 {padding-bottom:1em;}
+.pl1 {padding-left:1em;}
+.pt2 {padding-top:2em;}
+.pr2 {padding-right:2em;}
+.pb2 {padding-bottom:2em;}
+.pl2 {padding-left:2em;}
+.red {color: red; text-align: center;}
+.bold {font-weight: bold;}
+.clr {clear: both;}
+.txtctr {text-align: center;}
+
+#login, #password {
+ width: 120px;
+ margin: 2px 2px 8px;
+ padding: 4px;
+}
+
+input {
+ border: 1px solid #999;
+ -webkit-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
+ -moz-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
+ box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
+ width: 1.8em;
+}
+
+input[type=submit] {
+width:15em;
+background:#004F9B;
+color:#fff;
+height:30px;
+-webkit-border-radius: 15px;
+-moz-border-radius: 15px;
+border-radius: 15px;
+border: 1px solid #999;
+text-decoration: none;
+padding: 0.4em;
+}
+
+input[type=submit]:hover {
+background:#fff;
+color:#004F9B;
+}
+header {
+ margin-bottom: 2em;
+ text-align: center;
+}
+#top-bg {
+ width: 100%;
+ position: relative;
+ top: -4px;
+ height: 18px;
+}
+header h1 {
+ margin-top: 2em;
+}
+header h1,header h4 {
+ text-align: center;
+}
+
+section {
+ padding: 0 1em;
+}
+
+footer {
+ text-align: center;
+ margin-top: 2em;
+ border-top: 1px solid #222;
+}
+#pageresults, #pageadmin {
+ text-align: center;
+ width: 90%;
+ margin: 0 auto;
+ min-height: 160px;
+}
+.result_pres {
+ width: 20%;
+ display: inline-block;
+ font-size: 1.2em;
+ font-weight: bold;
+}
+.result_pres_cs {
+ width: 25%;
+ display: inline-block;
+ font-size: 1.2em;
+ font-weight: bold;
+ padding: 0.5em 0;
+}
+.conf_resolution {
+ height:5em;
+}
+.conf_cs {
+ width: 20%;
+}
+.conf_global_form { margin: 0; padding: 0; list-style: none }
+.conf_global_form li { overflow: auto }
+.conf_global_form li + li { margin-top: 5px }
+
+.conf_global_form label { float: left; width: auto; margin-right: 10px }
+.conf_global_form label + div { width: auto; overflow: auto }
+.conf_global_form input, .conf_global_form textarea { width: 100%; -moz-box-sizing: border-box; box-sizing: border-box }
+
+.admin-boxes {
+ width: 200px;
+ height: 100px;
+ display: inline-block;
+ background-color: #EEEEEE;
+ border-radius: 8px;
+ padding: 1em;
+ margin: 1em;
+ vertical-align: bottom;
+}
+.admin-boxes span {font-size: 1.2em; font-weight: bold; line-height: 40px;}
+
+.onoffswitch {
+position: relative; width: 90px;
+-webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
+left: 28%;
+top: 25%;
+}
+.onoffswitch-checkbox {
+display: none;
+}
+.onoffswitch-label {
+display: block; overflow: hidden; cursor: pointer;
+border: 2px solid #999999; border-radius: 20px;
+}
+.onoffswitch-inner {
+width: 200%; margin-left: -100%;
+-moz-transition: margin 0.3s ease-in 0s; -webkit-transition: margin 0.3s ease-in 0s;
+-o-transition: margin 0.3s ease-in 0s; transition: margin 0.3s ease-in 0s;
+}
+.onoffswitch-inner:before, .onoffswitch-inner:after {
+float: left; width: 50%; height: 30px; padding: 0; line-height: 30px;
+font-size: 14px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
+-moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;
+}
+.onoffswitch-inner:before {
+content: "Fermé";
+padding-right: 15px;
+background-color: #D91912; color: #FFFFFF;
+}
+.onoffswitch-inner:after {
+content: "Ouvert";
+padding-right: 10px;
+background-color: #09BA0A; color: #FFFFFF;
+text-align: right;
+}
+.onoffswitch-switch {
+width: 18px; margin: 6px;
+background: #FFFFFF;
+border: 2px solid #999999; border-radius: 20px;
+position: absolute; top: 0; bottom: 0; right: 56px;
+-moz-transition: all 0.3s ease-in 0s; -webkit-transition: all 0.3s ease-in 0s;
+-o-transition: all 0.3s ease-in 0s; transition: all 0.3s ease-in 0s;
+}
+.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
+margin-left: 0;
+}
+.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
+right: 0px;
+}
+/* Popup */
+.ui-dialog, .ui-widget {
+ background-color: #EA6B6B;
+ padding: 1em;
+}
+.ui-corner-all {
+ border-radius: 1em;
+}
+.ui-dialog-titlebar-close {
+ margin-left: 6em;
+ padding: 4px;
+}
+.raz-mdp a {text-decoration: none;}
+/* ==========================================================================
+ Helper classes
+ ========================================================================== */
+
+/*
+ * Image replacement
+ */
+
+.ir {
+ background-color: transparent;
+ border: 0;
+ overflow: hidden;
+ /* IE 6/7 fallback */
+ *text-indent: -9999px;
+}
+
+.ir:before {
+ content: "";
+ display: block;
+ width: 0;
+ height: 150%;
+}
+
+/*
+ * Hide from both screenreaders and browsers: h5bp.com/u
+ */
+
+.hidden {
+ display: none !important;
+ visibility: hidden;
+}
+
+/*
+ * Hide only visually, but have it available for screenreaders: h5bp.com/v
+ */
+
+.visuallyhidden {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+
+/*
+ * Extends the .visuallyhidden class to allow the element to be focusable
+ * when navigated to via the keyboard: h5bp.com/p
+ */
+
+.visuallyhidden.focusable:active,
+.visuallyhidden.focusable:focus {
+ clip: auto;
+ height: auto;
+ margin: 0;
+ overflow: visible;
+ position: static;
+ width: auto;
+}
+
+/*
+ * Hide visually and from screenreaders, but maintain layout
+ */
+
+.invisible {
+ visibility: hidden;
+}
+
+/*
+ * Clearfix: contain floats
+ *
+ * For modern browsers
+ * 1. The space content is one way to avoid an Opera bug when the
+ * `contenteditable` attribute is included anywhere else in the document.
+ * Otherwise it causes space to appear at the top and bottom of elements
+ * that receive the `clearfix` class.
+ * 2. The use of `table` rather than `block` is only necessary if using
+ * `:before` to contain the top-margins of child elements.
+ */
+
+.clearfix:before,
+.clearfix:after {
+ content: " "; /* 1 */
+ display: table; /* 2 */
+}
+
+.clearfix:after {
+ clear: both;
+}
+
+/*
+ * For IE 6/7 only
+ * Include this rule to trigger hasLayout and contain floats.
+ */
+
+.clearfix {
+ *zoom: 1;
+}
+
+/* ==========================================================================
+ EXAMPLE Media Queries for Responsive Design.
+ These examples override the primary ('mobile first') styles.
+ Modify as content requires.
+ ========================================================================== */
+
+@media only screen and (min-width: 832px) {
+ #identform {
+ width: 40%;
+ vertical-align: top;
+ display: inline-block;
+ margin: 0 4%;
+ height: 12em;
+ }
+ #checkvoteform {
+ width: 40%;
+ vertical-align: top;
+ display: inline-block;
+ margin: 0 2%;
+ height: 12em;
+ }
+ #identform input, #checkvoteform input {
+ width: 70%
+ }
+ #pagevote {
+ width: 50%;
+ }
+ #pagefinal {
+ width: 50%;
+ }
+}
+
+@media print,
+ (-o-min-device-pixel-ratio: 5/4),
+ (-webkit-min-device-pixel-ratio: 1.25),
+ (min-resolution: 120dpi) {
+ /* Style adjustments for high resolution devices */
+}
+
+/* ==========================================================================
+ Print styles.
+ Inlined to avoid required HTTP connection: h5bp.com/r
+ ========================================================================== */
+
+@media print {
+ * {
+ background: transparent !important;
+ color: #000 !important; /* Black prints faster: h5bp.com/s */
+ box-shadow: none !important;
+ text-shadow: none !important;
+ }
+
+ a,
+ a:visited {
+ text-decoration: underline;
+ }
+
+ a[href]:after {
+ content: " (" attr(href) ")";
+ }
+
+ abbr[title]:after {
+ content: " (" attr(title) ")";
+ }
+
+ /*
+ * Don't show links for images, or javascript/internal links
+ */
+
+ .ir a:after,
+ a[href^="javascript:"]:after,
+ a[href^="#"]:after {
+ content: "";
+ }
+
+ pre,
+ blockquote {
+ border: 1px solid #999;
+ page-break-inside: avoid;
+ }
+
+ thead {
+ display: table-header-group; /* h5bp.com/t */
+ }
+
+ tr,
+ img {
+ page-break-inside: avoid;
+ }
+
+ img {
+ max-width: 100% !important;
+ }
+
+ @page {
+ margin: 0.5cm;
+ }
+
+ p,
+ h2,
+ h3 {
+ orphans: 3;
+ widows: 3;
+ }
+
+ h2,
+ h3 {
+ page-break-after: avoid;
+ }
+}
diff --git a/admin/css/normalize.css b/admin/css/normalize.css
new file mode 100644
index 0000000..144ebbf
--- /dev/null
+++ b/admin/css/normalize.css
@@ -0,0 +1,527 @@
+/*! normalize.css v1.1.3 | MIT License | git.io/normalize */
+
+/* ==========================================================================
+ HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
+ */
+
+audio,
+canvas,
+video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
+ * Known issue: no IE 6 support.
+ */
+
+[hidden] {
+ display: none;
+}
+
+/* ==========================================================================
+ Base
+ ========================================================================== */
+
+/**
+ * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
+ * `em` units.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-size: 100%; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Address `font-family` inconsistency between `textarea` and other form
+ * elements.
+ */
+
+html,
+button,
+input,
+select,
+textarea {
+ font-family: sans-serif;
+}
+
+/**
+ * Address margins handled incorrectly in IE 6/7.
+ */
+
+body {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Links
+ ========================================================================== */
+
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+
+a:focus {
+ outline: thin dotted;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* ==========================================================================
+ Typography
+ ========================================================================== */
+
+/**
+ * Address font sizes and margins set differently in IE 6/7.
+ * Address font sizes within `section` and `article` in Firefox 4+, Safari 5,
+ * and Chrome.
+ */
+
+h1 {
+ font-size: 1.6em;
+ margin: 0.67em 0;
+}
+
+h2 {
+ font-size: 1.4em;
+ margin: 0.83em 0;
+}
+
+h3 {
+ font-size: 1.17em;
+ margin: 1em 0;
+}
+
+h4 {
+ font-size: 1em;
+ margin: 1.33em 0;
+}
+
+h5 {
+ font-size: 0.83em;
+ margin: 1.67em 0;
+}
+
+h6 {
+ font-size: 0.67em;
+ margin: 2.33em 0;
+}
+
+/**
+ * Address styling not present in IE 7/8/9, Safari 5, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+blockquote {
+ margin: 1em 40px;
+}
+
+/**
+ * Address styling not present in Safari 5 and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ * Known issue: no IE 6/7 normalization.
+ */
+
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Address styling not present in IE 6/7/8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Address margins set differently in IE 6/7.
+ */
+
+p,
+pre {
+ margin: 1em 0;
+}
+
+/**
+ * Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, serif;
+ _font-family: 'courier new', monospace;
+ font-size: 1em;
+}
+
+/**
+ * Improve readability of pre-formatted text in all browsers.
+ */
+
+pre {
+ white-space: pre;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+/**
+ * Address CSS quotes not supported in IE 6/7.
+ */
+
+q {
+ quotes: none;
+}
+
+/**
+ * Address `quotes` property not supported in Safari 4.
+ */
+
+q:before,
+q:after {
+ content: '';
+ content: none;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* ==========================================================================
+ Lists
+ ========================================================================== */
+
+/**
+ * Address margins set differently in IE 6/7.
+ */
+
+dl,
+menu,
+ol,
+ul {
+ margin: 1em 0;
+}
+
+dd {
+ margin: 0 0 0 40px;
+}
+
+/**
+ * Address paddings set differently in IE 6/7.
+ */
+
+menu,
+ol,
+ul {
+ padding: 0 0 0 40px;
+}
+
+/**
+ * Correct list images handled incorrectly in IE 7.
+ */
+
+nav ul,
+nav ol {
+ list-style: none;
+ list-style-image: none;
+}
+
+/* ==========================================================================
+ Embedded content
+ ========================================================================== */
+
+/**
+ * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
+ * 2. Improve image quality when scaled in IE 7.
+ */
+
+img {
+ border: 0; /* 1 */
+ -ms-interpolation-mode: bicubic; /* 2 */
+}
+
+/**
+ * Correct overflow displayed oddly in IE 9.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* ==========================================================================
+ Figures
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
+ */
+
+figure {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Forms
+ ========================================================================== */
+
+/**
+ * Correct margin displayed oddly in IE 6/7.
+ */
+
+form {
+ margin: 0;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct color not being inherited in IE 6/7/8/9.
+ * 2. Correct text not wrapping in Firefox 3.
+ * 3. Correct alignment displayed oddly in IE 6/7.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0;
+ white-space: normal; /* 2 */
+ *margin-left: -7px; /* 3 */
+}
+
+/**
+ * 1. Correct font size not being inherited in all browsers.
+ * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
+ * and Chrome.
+ * 3. Improve appearance and consistency in all browsers.
+ */
+
+button,
+input,
+select,
+textarea {
+ font-size: 100%; /* 1 */
+ margin: 0; /* 2 */
+ vertical-align: baseline; /* 3 */
+ *vertical-align: middle; /* 3 */
+}
+
+/**
+ * Address Firefox 3+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+ line-height: normal;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
+ * Known issue: inner spacing remains in IE 6.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+ *overflow: visible; /* 4 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * 1. Address box sizing set to content-box in IE 8/9.
+ * 2. Remove excess padding in IE 8/9.
+ * 3. Remove excess padding in IE 7.
+ * Known issue: excess padding remains in IE 6.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+ *height: 13px; /* 3 */
+ *width: 13px; /* 3 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
+ * (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box; /* 2 */
+ box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Remove inner padding and border in Firefox 3+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * 1. Remove default vertical scrollbar in IE 6/7/8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+
+textarea {
+ overflow: auto; /* 1 */
+ vertical-align: top; /* 2 */
+}
+
+/* ==========================================================================
+ Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
diff --git a/admin/inc/ajax.php b/admin/inc/ajax.php
new file mode 100644
index 0000000..0439808
--- /dev/null
+++ b/admin/inc/ajax.php
@@ -0,0 +1,15 @@
+prepare($sql);
+ // On regarde si le vote est ouvert
+ $option = 'vote_ouvert';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ $stmt->bindParam(':value', $value, PDO::PARAM_STR);
+ // Exécution de la requête
+ $stmt->execute();
+ }
+?>
diff --git a/admin/inc/auth.inc.php b/admin/inc/auth.inc.php
new file mode 100644
index 0000000..de7e886
--- /dev/null
+++ b/admin/inc/auth.inc.php
@@ -0,0 +1,38 @@
+
diff --git a/admin/inc/checkauth.inc.php b/admin/inc/checkauth.inc.php
new file mode 100644
index 0000000..d13f76b
--- /dev/null
+++ b/admin/inc/checkauth.inc.php
@@ -0,0 +1,11 @@
+prepare($sql);
+ $stmt->bindParam(':nb_postes_cs', $nb_postes_cs);
+ $stmt->bindParam(':nb_resolutions_ago', $nb_resolutions_ago);
+ $stmt->bindParam(':nb_resolutions_age', $nb_resolutions_age);
+ $stmt->bindParam(':url_docs', $url_docs);
+ // Exécution de la requête
+ $stmt->execute();
+ $stmt = null;
+ header('Location: ../config.php');
+}
+
+// On modifie les résolutions d'AGO
+if (isset($_POST['res_ago_1'])) {
+ $sql="
+ UPDATE admin SET option_value =
+ CASE option_id
+ WHEN 20 THEN :res_ago_1
+ WHEN 21 THEN :res_ago_2
+ WHEN 22 THEN :res_ago_3
+ WHEN 23 THEN :res_ago_4
+ WHEN 24 THEN :res_ago_5
+ WHEN 25 THEN :res_ago_6
+ WHEN 26 THEN :res_ago_7
+ WHEN 27 THEN :res_ago_8
+ WHEN 28 THEN :res_ago_9
+ WHEN 29 THEN :res_ago_10
+ ELSE option_value
+ END
+ WHERE option_id IN (20,21,22,23,24,25,26,27,28,29)
+ ";
+
+ // Si un champ est vide, on le passe à NULL
+ // Inutile depuis que nb_res_agoolutions est là
+ $blank = NULL;
+ if (empty($_POST['res_ago_1'])) {$res_ago_1 = $blank;} else {$res_ago_1 = $_POST['res_ago_1'];}
+ if (empty($_POST['res_ago_2'])) {$res_ago_2 = $blank;} else {$res_ago_2 = $_POST['res_ago_2'];}
+ if (empty($_POST['res_ago_3'])) {$res_ago_3 = $blank;} else {$res_ago_3 = $_POST['res_ago_3'];}
+ if (empty($_POST['res_ago_4'])) {$res_ago_4 = $blank;} else {$res_ago_4 = $_POST['res_ago_4'];}
+ if (empty($_POST['res_ago_5'])) {$res_ago_5 = $blank;} else {$res_ago_5 = $_POST['res_ago_5'];}
+ if (empty($_POST['res_ago_6'])) {$res_ago_6 = $blank;} else {$res_ago_6 = $_POST['res_ago_6'];}
+ if (empty($_POST['res_ago_7'])) {$res_ago_7 = $blank;} else {$res_ago_7 = $_POST['res_ago_7'];}
+ if (empty($_POST['res_ago_8'])) {$res_ago_8 = $blank;} else {$res_ago_8 = $_POST['res_ago_8'];}
+ if (empty($_POST['res_ago_9'])) {$res_ago_9 = $blank;} else {$res_ago_9 = $_POST['res_ago_9'];}
+ if (empty($_POST['res_ago_10'])) {$res_ago_10 = $blank;} else {$res_ago_10 = $_POST['res_ago_10'];}
+
+ $stmt = $bdd->prepare($sql);
+ $stmt->bindParam(':res_ago_1', $res_ago_1);
+ $stmt->bindParam(':res_ago_2', $res_ago_2);
+ $stmt->bindParam(':res_ago_3', $res_ago_3);
+ $stmt->bindParam(':res_ago_4', $res_ago_4);
+ $stmt->bindParam(':res_ago_5', $res_ago_5);
+ $stmt->bindParam(':res_ago_6', $res_ago_6);
+ $stmt->bindParam(':res_ago_7', $res_ago_7);
+ $stmt->bindParam(':res_ago_8', $res_ago_8);
+ $stmt->bindParam(':res_ago_9', $res_ago_9);
+ $stmt->bindParam(':res_ago_10', $res_ago_10);
+ // Exécution de la requête
+ $stmt->execute();
+ $stmt = null;
+ header('Location: ../config.php');
+}
+
+// On modifie les résolutions d'AGE
+if (isset($_POST['res_age_1'])) {
+ $sql="
+ UPDATE admin SET option_value =
+ CASE option_id
+ WHEN 30 THEN :res_age_1
+ WHEN 31 THEN :res_age_2
+ WHEN 32 THEN :res_age_3
+ WHEN 33 THEN :res_age_4
+ WHEN 34 THEN :res_age_5
+ WHEN 35 THEN :res_age_6
+ WHEN 36 THEN :res_age_7
+ WHEN 37 THEN :res_age_8
+ WHEN 38 THEN :res_age_9
+ WHEN 39 THEN :res_age_10
+ ELSE option_value
+ END
+ WHERE option_id IN (30,31,32,33,34,35,36,37,38,39)
+ ";
+
+ // Si un champ est vide, on le passe à NULL
+ // Inutile depuis que nb_res_ageolutions est là
+ $blank = NULL;
+ if (empty($_POST['res_age_1'])) {$res_age_1 = $blank;} else {$res_age_1 = $_POST['res_age_1'];}
+ if (empty($_POST['res_age_2'])) {$res_age_2 = $blank;} else {$res_age_2 = $_POST['res_age_2'];}
+ if (empty($_POST['res_age_3'])) {$res_age_3 = $blank;} else {$res_age_3 = $_POST['res_age_3'];}
+ if (empty($_POST['res_age_4'])) {$res_age_4 = $blank;} else {$res_age_4 = $_POST['res_age_4'];}
+ if (empty($_POST['res_age_5'])) {$res_age_5 = $blank;} else {$res_age_5 = $_POST['res_age_5'];}
+ if (empty($_POST['res_age_6'])) {$res_age_6 = $blank;} else {$res_age_6 = $_POST['res_age_6'];}
+ if (empty($_POST['res_age_7'])) {$res_age_7 = $blank;} else {$res_age_7 = $_POST['res_age_7'];}
+ if (empty($_POST['res_age_8'])) {$res_age_8 = $blank;} else {$res_age_8 = $_POST['res_age_8'];}
+ if (empty($_POST['res_age_9'])) {$res_age_9 = $blank;} else {$res_age_9 = $_POST['res_age_9'];}
+ if (empty($_POST['res_age_10'])) {$res_age_10 = $blank;} else {$res_age_10 = $_POST['res_age_10'];}
+
+ $stmt = $bdd->prepare($sql);
+ $stmt->bindParam(':res_age_1', $res_age_1);
+ $stmt->bindParam(':res_age_2', $res_age_2);
+ $stmt->bindParam(':res_age_3', $res_age_3);
+ $stmt->bindParam(':res_age_4', $res_age_4);
+ $stmt->bindParam(':res_age_5', $res_age_5);
+ $stmt->bindParam(':res_age_6', $res_age_6);
+ $stmt->bindParam(':res_age_7', $res_age_7);
+ $stmt->bindParam(':res_age_8', $res_age_8);
+ $stmt->bindParam(':res_age_9', $res_age_9);
+ $stmt->bindParam(':res_age_10', $res_age_10);
+ // Exécution de la requête
+ $stmt->execute();
+ $stmt = null;
+ header('Location: ../config.php');
+}
+
+// On modifie les candidats au CS
+if (isset($_POST['CS'])) {
+ $nb_vote_cs = count($_POST['CS']);
+ $vote_cs = implode(",", $_POST['CS']);
+ // On retire les éventuelles double virgules pour éviter un champ vide au milieu du formulaire
+ $vote_cs = str_replace(",,", ",", $vote_cs);
+ // On retire l'enventuelle virgule finale pour éviter un champ vide à la fin du formulaire
+ $vote_cs = rtrim($vote_cs, ",");
+ $sql="UPDATE admin SET option_value = :vote_cs WHERE option_id = 5";
+ $stmt = $bdd->prepare($sql);
+ $stmt->bindParam(':vote_cs', $vote_cs);
+ // Exécution de la requête
+ $stmt->execute();
+ $stmt = null;
+ header('Location: ../config.php');
+}
diff --git a/admin/inc/config.inc.php b/admin/inc/config.inc.php
new file mode 100644
index 0000000..ef34af1
--- /dev/null
+++ b/admin/inc/config.inc.php
@@ -0,0 +1,6 @@
+ 'SET NAMES utf8');
+ $bdd = new PDO('mysql:host='.$PARAM_host.';dbname='.$PARAM_dbname, $PARAM_user, $PARAM_password, $arrExtraParam);
+ $bdd->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+}
+
+catch(PDOException $e)
+{
+ // Display dev or prod error message
+ $msg = 'ERREUR PDO dans ' . $e->getFile() . ' Ligne ' . $e->getLine() . ' : ' . $e->getMessage();
+ //$msg = "Une erreur PDO est survenue !";
+ die($msg);
+}
diff --git a/admin/inc/disconnect.inc.php b/admin/inc/disconnect.inc.php
new file mode 100644
index 0000000..5679418
--- /dev/null
+++ b/admin/inc/disconnect.inc.php
@@ -0,0 +1,12 @@
+ $longueurMax) {
+ $longueur = $longueurMax;
+ }
+
+ $i = 0;
+ // Ajouter un caractère aléatoire à $mdp jusqu'à ce que $longueur soit atteint
+ while ($i < $longueur) {
+ // Prendre un caractère aléatoire
+ $caractere = substr($possible, mt_rand(0, $longueurMax-1), 1);
+
+ // Vérifier si le caractère est déjà utilisé dans $mdp
+ if (!strstr($mdp, $caractere)) {
+ // Si non, ajouter le caractère à $mdp et augmenter le compteur
+ $mdp .= $caractere;
+ $i++;
+ }
+ }
+ return $mdp;
+}
+
+// Fonction de vérification du couple identifiant mot de passe
+function verification($reference,$password) {
+ include("config.inc.php");
+ include("connect.inc.php");
+
+ $reference_sql = $bdd->quote($reference);
+ $password_sql = $bdd->quote($password);
+
+ $sql = "SELECT count(*) as number from clients WHERE reference=$reference_sql AND password=$password_sql";
+
+ $result = $bdd->query($sql);
+ $row = $result->fetch();
+ $result = null;
+ if ($row['number'] == 1) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+// Sécurisation des données en sortie
+function secure($var) {
+ $var = htmlspecialchars(strip_tags($var));
+ return $var;
+}
+
+// Vérification de la tenue du AGE
+function checkago() {
+ include("inc/config.inc.php");
+ include("inc/connect.inc.php");
+ $sql = "SELECT option_name, option_value FROM admin WHERE option_name = :option";
+ $stmt = $bdd->prepare($sql);
+ $option = 'resolution_ago_nb';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ $stmt->execute();
+ $results = $stmt->fetch();
+ if ($results['option_value'] == 0) return false; else return true;
+}
+
+// Vérification de la tenue du AGE
+function checkage() {
+ include("inc/config.inc.php");
+ include("inc/connect.inc.php");
+ $sql = "SELECT option_name, option_value FROM admin WHERE option_name = :option";
+ $stmt = $bdd->prepare($sql);
+ $option = 'resolution_age_nb';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ $stmt->execute();
+ $results = $stmt->fetch();
+ if ($results['option_value'] == 0) return false; else return true;
+}
+
+// Vérification de la tenue du vote du CS
+function checkcs() {
+ include("inc/config.inc.php");
+ include("inc/connect.inc.php");
+ $sql = "SELECT option_name, option_value FROM admin WHERE option_name = :option";
+ $stmt = $bdd->prepare($sql);
+ $option = 'cs_nb_poste';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ $stmt->execute();
+ $results = $stmt->fetch();
+ if ($results['option_value'] == 0) return false; else return true;
+}
diff --git a/admin/inc/generate.php b/admin/inc/generate.php
new file mode 100644
index 0000000..b39ed97
--- /dev/null
+++ b/admin/inc/generate.php
@@ -0,0 +1,54 @@
+query('SELECT reference,password FROM clients WHERE password IS NULL');
+$nb = $query->rowCount();
+// Si le retour est vide, on affiche un petit message
+if ($query->rowCount() > 0) {
+ // on génére un password pour chaque référence
+ while ($results = $query->fetch(PDO::FETCH_OBJ)) {
+ $ref = $results->reference;
+ $mdp = passwdgen(10);
+
+ // Préparation de la requête
+ $sql ="
+ UPDATE clients
+ SET password = :mdp
+ WHERE reference = :ref
+ ";
+ $stmt = $bdd->prepare($sql);
+ $stmt->bindParam(':mdp', $mdp, PDO::PARAM_STR);
+ $stmt->bindParam(':ref', $ref, PDO::PARAM_STR);
+
+ // Exécution de la requête
+ $stmt->execute();
+
+ // On enregistre que les mots de passe sont générés
+ $sql ="
+ UPDATE admin
+ SET option_value = 1
+ WHERE option_id = 9
+ ";
+ $stmt = $bdd->prepare($sql);
+ // Exécution de la requête
+ $stmt->execute();
+
+ // Clore la requête
+ $stmt->closeCursor();
+ $stmt = NULL;
+ header('Location: ../index.php?$generate=true&$nb='.$nb);
+
+ }
+} else {header('Location: ../index.php?$generate=false');}
+?>
diff --git a/admin/inc/libchart.php b/admin/inc/libchart.php
new file mode 100644
index 0000000..d967ba5
--- /dev/null
+++ b/admin/inc/libchart.php
@@ -0,0 +1,43 @@
+.
+ *
+ */
+
+ require_once 'model/ChartConfig.php';
+ require_once 'model/Point.php';
+ require_once 'model/DataSet.php';
+ require_once 'model/XYDataSet.php';
+ require_once 'model/XYSeriesDataSet.php';
+
+ require_once 'view/primitive/Padding.php';
+ require_once 'view/primitive/Rectangle.php';
+ require_once 'view/primitive/Primitive.php';
+ require_once 'view/text/Text.php';
+ require_once 'view/color/Color.php';
+ require_once 'view/color/ColorSet.php';
+ require_once 'view/color/Palette.php';
+ require_once 'view/axis/Bound.php';
+ require_once 'view/axis/Axis.php';
+ require_once 'view/plot/Plot.php';
+ require_once 'view/caption/Caption.php';
+ require_once 'view/chart/Chart.php';
+ require_once 'view/chart/BarChart.php';
+ require_once 'view/chart/VerticalBarChart.php';
+ require_once 'view/chart/HorizontalBarChart.php';
+ require_once 'view/chart/LineChart.php';
+ require_once 'view/chart/PieChart.php';
+?>
diff --git a/admin/inc/mail-php.inc.php b/admin/inc/mail-php.inc.php
new file mode 100644
index 0000000..0c2ccb7
--- /dev/null
+++ b/admin/inc/mail-php.inc.php
@@ -0,0 +1,122 @@
+query('SELECT * FROM clients WHERE vote = 0');
+// Si le retour est vide, on affiche un petit message
+$nb = $query->rowCount();
+if ($query->rowCount() > 0) {
+ // On affiche les resultats
+ while ($results = $query->fetch(PDO::FETCH_OBJ)) {
+ // recipients
+ $to = $results->email;
+ // subject
+ $subject = 'Ouvaton : Convocation AG 2015 et vote en ligne';
+ // message
+ $message = "
+
+
+ Ouvaton.coop AG 2015
+
+
+
CECI EST UN TEST INTERNE
+
Cher Coopérateur/Chère Coopératrice,
+
+
Nous avons le plaisir de vous indiquer que les coopérateurs et coopératrices de la société OUVATON sont convoqués en Assemblée Générale Ordinaire le vendredi 30 mai 2014 à 21h à
+ Locmaria Guidel, afin de délibérer sur l’ordre du jour suivant :
+
+
+
Présentation du rapport moral et financier par le Président du Directoire
+
Présentation du rapport du Conseil de Surveillance
+
Approbation des comptes 2013
+
Affectation du résultat
+
Quitus au Directoire et Conseil de Surveillance pour ledit exercice
+
Élection des membres du Conseil de Surveillance
+
Questions diverses
+
Pouvoirs pour formalités.
+
+
+
Dans l'hypothèse où le quorum ne serait pas atteint suite à cette première convocation, une deuxième assemblée serait convoquée le samedi 7 juin 2014 à à la Mairie du 2ème arrondissement,
+ 8 rue de la Banque 75002 Paris, à 10h. Les votes émis avant le 30 mai 2014 à 21h resteront valables pour l'éventuelle assemblée de seconde convocation.
+
+
Nous vous rappelons que le droit de participer à l’assemblée est subordonné à l’inscription de titres sur le registre des parts sociales de la coopérative au jour de l’Assemblée Générale.
+
+
Vous trouverez tous les documents relatifs à l'Assemblée Générale 2014 statuant sur l'exercice 2013 (documents comptables, rapports, candidatures) sur le site de la
+ Coopérative en https://www.ouvaton.coop/Assemblee-Generale-2014.
+ Ils peuvent être discutées sur les forums Ouvaton.
+
+
Conformément à la possibilité ouverte par la résolution n°5 adoptée par l'Assemblée Générale du 4 mai 2013, le vote sur les résolutions s'effectuera par scrutin électronique sécurisé
+ (voir modalités plus bas).
+
+
Le formulaire de vote en ligne de notre Assemblée Générale est disponible sur https://ag.ouvaton.coop.
+
+
Voici les instructions pour voter :
+
+
Quand elles vous seront demandées saisissez les informations suivantes :
+
+
+
Numéro de coopérateur : ".$results->reference."
+
Code d'accès : ".$results->password."
+
+
+
Le vote se déroule en 4 étapes :
+
+
D'abord vous devez vous identifier en utilisant votre numéro de coopérateur et le code d'accès donné ci-dessus.
+
+
Ensuite vous devez compléter le questionnaire avec vos choix.
+
+
Dans la troisième étape vos choix seront affichés, et il vous sera demandé de les confirmer, ou de retourner à l'étape précédente si vous souhaitez modifier votre vote.
+
+
Enfin, après avoir confirmé votre choix, un clé de contrôle unique vous sera donné, qui vous permettra de vérifier que votre réponse a été correctement enregistrée. Pour assurer l'anonymat, aucun
+ lien ne sera conservé entre cette clé et vos identifiants d'accès, gardez donc précieusement cette clé. Une fois que vous aurez voté, vous ne pourrez plus le faire à nouveau.
+
+
Merci par avance pour votre participation à ce vote électronique.
+
+
Cordialement,
+ Le Président du Directoire
+
Ouvaton Coopérative SA à directoire et conseil de surveillance à capital variable Siège Social 16 bis rue d’Odessa 75014 PARIS R.C.S. Paris 438 168 718
+
+
+ ";
+
+ // To send HTML mail, the Content-type header must be set
+ $headers = 'MIME-Version: 1.0' . "\r\n";
+ $headers .= 'Content-type: text/html; charset=utf-8' . "\r\n";
+
+ // Additional headers
+ $headers .= 'From: Ouvaton.coop AG 2015 ' . "\r\n";
+
+ // Mail it
+ mail($to, $subject, $message, $headers);
+ }
+
+ // On actualise la date et on incrémente le compteur
+$nb_mail_sent = $_SESSION['nb_mail_sent'] + 1;
+ $sql ="
+ UPDATE admin SET option_value =
+ CASE
+ WHEN option_id = 2 THEN :last_mail_sent
+ WHEN option_id = 3 THEN :nb_mail_sent
+ ELSE option_value
+ END
+ WHERE option_id IN (2,3)
+ ";
+ $stmt = $bdd->prepare($sql);
+ $stmt->bindParam(':last_mail_sent', date("Y-m-d H:i:s"));
+ $stmt->bindParam(':nb_mail_sent', $nb_mail_sent);
+ // Exécution de la requête
+ $stmt->execute();
+ //Clore la requête
+ $stmt = null;
+ header('Location: ../index.php?$sent=true&$nb='.$nb);
+} else {echo '
Aucune données !
';}
diff --git a/admin/inc/mail.inc.php b/admin/inc/mail.inc.php
new file mode 100644
index 0000000..a97de8c
--- /dev/null
+++ b/admin/inc/mail.inc.php
@@ -0,0 +1,324 @@
+prepare("SELECT * FROM clients WHERE vote = 0 AND mailed = ? LIMIT 75");
+ $query->execute(array(0));
+ // Si tout le monde n'a pas voté, on balance les mails
+ $nbs = $query->rowCount();
+ if ($query->rowCount() > 0) {
+ // On utilise PHPMailer
+ while ($results = $query->fetch(PDO::FETCH_OBJ)) {
+ $mail = new PHPMailer;
+ $mail->isSMTP();
+ $mail->CharSet = "UTF-8";
+ //Enable SMTP debugging
+ // 0 = off (for production use)
+ // 1 = client messages
+ // 2 = client and server messages
+ //$mail->SMTPDebug = 2;
+ //Ask for HTML-friendly debug output
+ //$mail->Debugoutput = 'html';
+ // To load the French version
+ $mail->setLanguage('fr', 'PHPMailer/language/');
+ $mail->Host = 'smtp.ouvaton.coop';
+ $mail->SMTPAuth = true;
+ $mail->SMTPKeepAlive = true;
+ $mail->Username = '';
+ $mail->Password = '';
+ $mail->SMTPSecure = 'tls';
+ $mail->Port = 587;
+
+ $mail->From = '';
+ $mail->FromName = 'Ouvaton.coop AG 2019';
+ $mail->addAddress($results->email);
+ $mail->addReplyTo('');
+
+ $mail->isHTML(true);
+ $mail->Subject = 'Ouvaton AG 2019 : convocation et vote en ligne';
+
+ // On prépare le contenu du mail
+ $body = "
+
+
+ Ouvaton.coop AG 2019
+
+
+
Cher Coopérateur/Chère Coopératrice,
+
+
Vous détenez une ou plusieurs parts sociales de la Coopérative Ouvaton. À ce titre, nous avons le plaisir de vous indiquer que les sociétaires sont convoqués en Assemblée Générale Ordinaire le vendredi 19 juin 2019 à 21h à Locmaria Guidel, afin de délibérer sur l’ordre du jour suivant :
+
+
+
Présentation du rapport moral et financier par le Président du Directoire
+
Présentation du rapport du Conseil de Surveillance
+
Approbation des comptes 2018
+
Affectation du résultat
+
Quitus au Directoire et Conseil de Surveillance pour ledit exercice
+
Élection des membres du Conseil de Surveillance
+
Questions diverses
+
Pouvoirs pour formalités
+
+
+
Dans l'hypothèse où le quorum ne serait pas atteint suite à cette première convocation, une deuxième assemblée serait convoquée le samedi 29 juin 2019, au 187 montée de Choulans 69005 Lyon, à 13h. Les votes émis avant le 19 juin 2019 à 21h resteront valables pour l'éventuelle assemblée de seconde convocation.
Conformément à la possibilité ouverte par la résolution n°5 adoptée par l'Assemblée Générale du 4 mai 2013, le vote sur les résolutions s'effectuera par scrutin électronique sécurisé (voir modalités plus bas).
+
+
Le formulaire de vote en ligne de notre Assemblée Générale est disponible sur https://ag.ouvaton.coop.
+
+
Il est accessible dès maintenant et le restera jusqu'au jour de l'Assemblée Générale à 15h.
+ Nous attirons votre attention sur le fait que vous ne pourrez pas corriger votre vote une fois que vous l'aurez émis.
+ Vous pouvez prendre le temps de la réflexion, une nouvelle invitation à voter vous sera envoyée dans deux semaines, puis quelques jours avant l'assemblée de seconde convocation.
+
+
Voici les instructions pour voter :
+
+
Quand elles vous seront demandées, saisissez les informations suivantes :
+
+
+
Numéro de coopérateur : ".$results->reference."
+
Mot de passe : ".$results->password."
+
+
+
Le vote se déroule en 4 étapes :
+
+
1. Vous devez vous identifier en utilisant votre numéro de coopérateur et le code d'accès donné ci-dessus.
+
+
2. Vous devez compléter le questionnaire avec vos choix.
+
+
3. Vos choix seront affichés, et il vous sera demandé de les confirmer, ou de retourner à l'étape précédente si vous souhaitez modifier votre vote.
+
+
4. Après avoir confirmé vos choix, une clé de contrôle unique vous sera donnée, qui vous permettra de vérifier que votre réponse a été correctement enregistrée. Pour assurer l'anonymat, aucun lien ne sera conservé entre cette clé et vos identifiants d'accès, gardez donc précieusement cette clé.
+ Une fois que vous aurez voté, vous ne pourrez plus le faire à nouveau.
+
+
Merci par avance pour votre participation à ce vote électronique.
+
+
Coopérativement,
+ Le Président du Directoire
+
Ouvaton Coopérative SA à directoire et conseil de surveillance à capital variable Siège Social 16 bis rue d’Odessa 75014 PARIS R.C.S. Paris 438 168 718
+
+
+ ";
+ $mail->msgHTML($body);
+ if ($mail->send()) {
+ // On fait le ménage dans PHPMailer avant la prochaine boucle
+ $mail->clearAddresses();
+ $mail->clearAttachments();
+
+ // On marque que le mail est envoyé pour ce coopérateur
+ $sql ="
+ UPDATE clients
+ SET mailed = :new_nb_mail_sent
+ WHERE reference = :reference
+ ";
+ $stmt = $bdd->prepare($sql);
+ $stmt->bindParam(':reference', $results->reference);
+ $stmt->bindParam(':new_nb_mail_sent', $new_nb_mail_sent);
+ // Exécution de la requête
+ $stmt->execute();
+ } else {echo 'Mailer Error: ' . $mail->ErrorInfo . ' ';}
+ }
+ // On regarde combien de coopérateurs n'ont pas encore de mails
+ $sql = "SELECT COUNT(*) FROM clients WHERE vote = 0 AND mailed = :nb_mail_sent";
+ $stmt = $bdd->prepare($sql);
+ $stmt->bindParam(':nb_mail_sent', $_SESSION['nb_mail_sent']);
+ // Exécution de la requête
+ $stmt->execute();
+ $results = $stmt->fetch();
+ $nbl = $results[0];
+
+ // On actualise la date et on incrémente le compteur
+ if ($nbl == 0) {
+ $nb_mail_sent = 1;
+ $sql ="
+ UPDATE admin SET option_value =
+ CASE
+ WHEN option_id = 2 THEN :last_mail_sent
+ WHEN option_id = 3 THEN :nb_mail_sent
+ ELSE option_value
+ END
+ WHERE option_id IN (2,3)
+ ";
+ $stmt = $bdd->prepare($sql);
+ $stmt->bindParam(':last_mail_sent', date("Y-m-d H:i:s"));
+ $stmt->bindParam(':nb_mail_sent', $nb_mail_sent);
+ // Exécution de la requête
+ $stmt->execute();
+ }
+ // Time Stop
+ $timeend=microtime(true);
+ $time=$timeend-$timestart;
+
+ //Exec Time
+ $page_load_time = number_format($time, 3);
+ echo "Start : ".date("H:i:s", $timestart);
+ echo " Stop: ".date("H:i:s", $timeend);
+ echo " Script execute en " . $page_load_time . " sec";
+ header('Location: ../index.php?$sent=true&$nbs='.$nbs.'&$nbl='.$nbl.'&$plt='.$page_load_time);
+ } else {echo '
Aucune données ?
';}
+} else {
+ // On envoie les mails par paquet de 75
+ $query = $bdd->prepare("SELECT * FROM clients WHERE vote = 0 AND mailed = ? LIMIT 75");
+ $query->execute(array($_SESSION['nb_mail_sent']));
+ // Si tout le monde n'a pas voté, on balance les mails
+ $nbs = $query->rowCount();
+ if ($query->rowCount() > 0) {
+ // On utilise PHPMailer
+ while ($results = $query->fetch(PDO::FETCH_OBJ)) {
+ $mail = new PHPMailer;
+ $mail->isSMTP();
+ $mail->CharSet = "UTF-8";
+ //Enable SMTP debugging
+ // 0 = off (for production use)
+ // 1 = client messages
+ // 2 = client and server messages
+ //$mail->SMTPDebug = 2;
+ //Ask for HTML-friendly debug output
+ //$mail->Debugoutput = 'html';
+ // To load the French version
+ $mail->setLanguage('fr', 'PHPMailer/language/');
+ $mail->Host = 'smtp.ouvaton.coop';
+ $mail->SMTPAuth = true;
+ $mail->SMTPKeepAlive = true;
+ $mail->Username = '';
+ $mail->Password = '';
+ $mail->SMTPSecure = 'tls';
+ $mail->Port = 587;
+
+ $mail->From = '';
+ $mail->FromName = 'Ouvaton.coop AG 2019';
+ $mail->addAddress($results->email);
+ $mail->addReplyTo('');
+
+ $mail->isHTML(true);
+ $mail->Subject = 'Ouvaton AG 2019 : quelques minutes pour participer au vote';
+
+ // On prépare le contenu du mail
+ $body = "
+
+
+ Ouvaton.coop AG 2019
+
+
+
Cher Coopérateur/Chère Coopératrice,
+
+
Plus que quelques semaines pour voter et participer à notre AG 2019 !
+
+
Notre AG se déroulera le vendredi 29 juin 2019 au 187 montée de Choulans 69005 Lyon, à 13h.
Le formulaire de vote en ligne de notre Assemblée Générale est disponible sur https://ag.ouvaton.coop.
+
+
Il est accessible dès maintenant et le restera jusqu'au jour de l'Assemblée Générale à 15h.
+ Nous attirons votre attention sur le fait que vous ne pourrez pas corriger votre vote une fois que vous l'aurez émis.
+
+
Voici les instructions pour voter :
+
+
Quand elles vous seront demandées saisissez les informations suivantes :
+
+
+
Numéro de coopérateur : ".$results->reference."
+
Mot de passe : ".$results->password."
+
+
+
Le vote se déroule en 4 étapes :
+
+
1. Vous devez vous identifier en utilisant votre numéro de coopérateur et le code d'accès donné ci-dessus.
+
+
2. Vous devez compléter le questionnaire avec vos choix.
+
+
3. Vos choix seront affichés, et il vous sera demandé de les confirmer, ou de retourner à l'étape précédente si vous souhaitez modifier votre vote.
+
+
4. Après avoir confirmé vos choix, une clé de contrôle unique vous sera donnée, qui vous permettra de vérifier que votre réponse a été correctement enregistrée. Pour assurer l'anonymat, aucun lien ne sera conservé entre cette clé et vos identifiants d'accès, gardez donc précieusement cette clé. Une fois que vous aurez voté, vous ne pourrez plus le faire à nouveau.
+
+
Merci par avance pour votre participation à ce vote électronique.
+
+
Coopérativement,
+ Le Président du Directoire
+
Ouvaton Coopérative SA à directoire et conseil de surveillance à capital variable Siège Social 16 bis rue d’Odessa 75014 PARIS R.C.S. Paris 438 168 718
+
+
+ ";
+ $mail->msgHTML($body);
+ if ($mail->send()) {
+ // On fait le ménage dans PHPMailer avant la prochaine boucle
+ $mail->clearAddresses();
+ $mail->clearAttachments();
+
+ // On marque que le mail est envoyé pour ce coopérateur
+ $sql ="
+ UPDATE clients
+ SET mailed = :new_nb_mail_sent
+ WHERE reference = :reference
+ ";
+ $stmt = $bdd->prepare($sql);
+ $stmt->bindParam(':reference', $results->reference);
+ $stmt->bindParam(':new_nb_mail_sent', $new_nb_mail_sent);
+ // Exécution de la requête
+ $stmt->execute();
+ } else {echo 'Mailer Error: ' . $mail->ErrorInfo . ' ';}
+ }
+
+ // On regarde combien de coopérateurs n'ont pas encore de mails
+ $sql = "SELECT COUNT(*) FROM clients WHERE vote = 0 AND mailed = :nb_mail_sent";
+ $stmt = $bdd->prepare($sql);
+ $stmt->bindParam(':nb_mail_sent', $_SESSION['nb_mail_sent']);
+ // Exécution de la requête
+ $stmt->execute();
+ $results = $stmt->fetch();
+ $nbl = $results[0];
+
+ // On actualise la date et on incrémente le compteur
+ if ($nbl == 0) {
+ $nb_mail_sent = $_SESSION['nb_mail_sent']+1;
+ $sql ="
+ UPDATE admin SET option_value =
+ CASE
+ WHEN option_id = 2 THEN :last_mail_sent
+ WHEN option_id = 3 THEN :nb_mail_sent
+ ELSE option_value
+ END
+ WHERE option_id IN (2,3)
+ ";
+ $stmt = $bdd->prepare($sql);
+ $stmt->bindParam(':last_mail_sent', date("Y-m-d H:i:s"));
+ $stmt->bindParam(':nb_mail_sent', $nb_mail_sent);
+ // Exécution de la requête
+ $stmt->execute();
+ }
+ //Clore la requête
+ $stmt = null;
+
+ // Time Stop
+ $timeend=microtime(true);
+ $time=$timeend-$timestart;
+
+ //Exec Time
+ $page_load_time = number_format($time, 3);
+ echo "Start : ".date("H:i:s", $timestart);
+ echo " Stop: ".date("H:i:s", $timeend);
+ echo " Script execute en " . $page_load_time . " sec";
+ header('Location: ../index.php?$sent=true&$nbs='.$nbs.'&$nbl='.$nbl.'&$plt='.$page_load_time);
+ } else {echo '
Aucune données !!
';}
+}
diff --git a/admin/inc/model/ChartConfig.php b/admin/inc/model/ChartConfig.php
new file mode 100644
index 0000000..4e2f026
--- /dev/null
+++ b/admin/inc/model/ChartConfig.php
@@ -0,0 +1,112 @@
+.
+ *
+ */
+
+ /**
+ * Configuration attributes of the chart.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ */
+ class ChartConfig {
+ /**
+ * Use several colors for a single data set chart (as if it was a multiple data set).
+ *
+ * @var Boolean
+ */
+ private $useMultipleColor;
+
+ /**
+ * Show caption on individual data points.
+ *
+ * @var Boolean
+ */
+ private $showPointCaption;
+
+ /**
+ * Sort data points (only pie charts).
+ *
+ * @var Boolean
+ */
+ private $sortDataPoint;
+
+ /**
+ * Creates a new ChartConfig with default options.
+ */
+ public function ChartConfig() {
+ $this->useMultipleColor = false;
+ $this->showPointCaption = true;
+ $this->sortDataPoint = true;
+ }
+
+ /**
+ * If true the chart will use several colors for a single data set chart
+ * (as if it was a multiple data set).
+ *
+ * @param $useMultipleColor Use several colors : boolean
+ */
+ public function setUseMultipleColor($useMultipleColor) {
+ $this->useMultipleColor = $useMultipleColor;
+ }
+
+ /**
+ * If true the chart will use several colors for a single data set chart
+ * (as if it was a multiple data set).
+ *
+ * @return $useMultipleColor Use several colors : boolean
+ */
+ public function getUseMultipleColor() {
+ return $this->useMultipleColor;
+ }
+
+ /**
+ * Set the option to show caption on individual data points.
+ *
+ * @param $showPointCaption Show caption on individual data points : boolean
+ */
+ public function setShowPointCaption($showPointCaption) {
+ $this->showPointCaption = $showPointCaption;
+ }
+
+ /**
+ * Get the option to show caption on individual data points.
+ *
+ * @return Show caption on individual data points : boolean
+ */
+ public function getShowPointCaption() {
+ return $this->showPointCaption;
+ }
+
+ /**
+ * Set the option to sort data points (only pie charts).
+ *
+ * @param $sortDataPoint Sort data points : boolean
+ */
+ public function setSortDataPoint($sortDataPoint) {
+ $this->sortDataPoint = $sortDataPoint;
+ }
+
+ /**
+ * Get the option to sort data points (only pie charts).
+ *
+ * @return Sort data points : boolean
+ */
+ public function getSortDataPoint() {
+ return $this->sortDataPoint;
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/model/DataSet.php b/admin/inc/model/DataSet.php
new file mode 100644
index 0000000..d4b332f
--- /dev/null
+++ b/admin/inc/model/DataSet.php
@@ -0,0 +1,28 @@
+.
+ *
+ */
+
+ /**
+ * Superclass of all data sets.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ * Created on 10 may 2007
+ */
+ abstract class DataSet {
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/model/Point.php b/admin/inc/model/Point.php
new file mode 100644
index 0000000..18aabc0
--- /dev/null
+++ b/admin/inc/model/Point.php
@@ -0,0 +1,59 @@
+.
+ *
+ */
+
+ /**
+ * Point of coordinates (X,Y).
+ * The value of X isn't really of interest, but X is used as a label to display on the horizontal axis.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ */
+ class Point {
+ private $x;
+ private $y;
+
+ /**
+ * Creates a new sampling point of coordinates (x, y)
+ *
+ * @param integer x coordinate (label)
+ * @param integer y coordinate (value)
+ */
+ public function Point($x, $y) {
+ $this->x = $x;
+ $this->y = $y;
+ }
+
+ /**
+ * Gets the x coordinate (label).
+ *
+ * @return integer x coordinate (label)
+ */
+ public function getX() {
+ return $this->x;
+ }
+
+ /**
+ * Gets the y coordinate (value).
+ *
+ * @return integer y coordinate (value)
+ */
+ public function getY() {
+ return $this->y;
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/model/XYDataSet.php b/admin/inc/model/XYDataSet.php
new file mode 100644
index 0000000..3b48096
--- /dev/null
+++ b/admin/inc/model/XYDataSet.php
@@ -0,0 +1,56 @@
+.
+ *
+ */
+
+ /**
+ * Set of data in the form of (x, y) items.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ * Created on 10 may 2007
+ */
+ class XYDataSet extends DataSet {
+ private $pointList;
+
+ /**
+ * Constructor of XYDataSet.
+ *
+ */
+ public function XYDataSet() {
+ $this->pointList = array();
+ }
+
+ /**
+ * Add a new point to the dataset.
+ *
+ * @param Point Point to add to the dataset
+ */
+
+ public function addPoint($point) {
+ array_push($this->pointList, $point);
+ }
+
+ /**
+ * Getter of pointList.
+ *
+ * @return List of points.
+ */
+ public function getPointList() {
+ return $this->pointList;
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/model/XYSeriesDataSet.php b/admin/inc/model/XYSeriesDataSet.php
new file mode 100644
index 0000000..6e8ad3f
--- /dev/null
+++ b/admin/inc/model/XYSeriesDataSet.php
@@ -0,0 +1,76 @@
+.
+ *
+ */
+
+ /**
+ * This dataset comprises several series of points and is used to plot multiple lines charts.
+ * Each serie is a XYDataSet.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ * Created on 20 july 2007
+ */
+ class XYSeriesDataSet extends DataSet {
+ /**
+ * List of titles
+ */
+ private $titleList;
+
+ /**
+ * List of XYDataSet.
+ */
+ private $serieList;
+
+ /**
+ * Constructor of XYSeriesDataSet.
+ *
+ */
+ public function XYSeriesDataSet() {
+ $this->titleList = array();
+ $this->serieList = array();
+ }
+
+ /**
+ * Add a new serie to the dataset.
+ *
+ * @param string Title (label) of the serie.
+ * @param XYDataSet Serie of points to add
+ */
+ public function addSerie($title, $serie) {
+ array_push($this->titleList, $title);
+ array_push($this->serieList, $serie);
+ }
+
+ /**
+ * Getter of titleList.
+ *
+ * @return List of titles.
+ */
+ public function getTitleList() {
+ return $this->titleList;
+ }
+
+ /**
+ * Getter of serieList.
+ *
+ * @return List of series.
+ */
+ public function getSerieList() {
+ return $this->serieList;
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/session.inc.php b/admin/inc/session.inc.php
new file mode 100644
index 0000000..ea8cad9
--- /dev/null
+++ b/admin/inc/session.inc.php
@@ -0,0 +1,11 @@
+prepare($sql);
+// Exécution de la requête
+$stmt->execute();
+
+// On enregistre que les mots de passe ne sont pas générés
+$sql ="UPDATE admin SET option_value = 0 WHERE option_id = 9";
+$stmt = $bdd->prepare($sql);
+// Exécution de la requête
+$stmt->execute();
+
+//Clore la requête
+$stmt = null;
+header('Location: ../index.php?$ungenerate=true');
diff --git a/admin/inc/view/axis/Axis.php b/admin/inc/view/axis/Axis.php
new file mode 100644
index 0000000..d649928
--- /dev/null
+++ b/admin/inc/view/axis/Axis.php
@@ -0,0 +1,130 @@
+.
+ *
+ */
+
+ /**
+ * Automatic axis boundaries and ticks calibration
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ */
+ class Axis {
+ private $min;
+ private $max;
+ private $guide;
+ private $delta;
+ private $magnitude;
+ private $displayMin;
+ private $displayMax;
+ private $tics;
+
+ /**
+ * Creates a new axis formatter.
+ *
+ * @param integer minimum value on the axis
+ * @param integer maximum value on the axis
+ */
+ public function Axis($min, $max) {
+ $this->min = $min;
+ $this->max = $max;
+
+ $this->guide = 10;
+ }
+
+ /**
+ * Computes value between two ticks.
+ */
+ public function quantizeTics() {
+ // Approximate number of decades, in [1..10[
+ $norm = $this->delta / $this->magnitude;
+
+ // Approximate number of tics per decade
+ $posns = $this->guide / $norm;
+
+ if ($posns > 20) {
+ $tics = 0.05; // e.g. 0, .05, .10, ...
+ } else if ($posns > 10) {
+ $tics = 0.2; // e.g. 0, .1, .2, ...
+ } else if ($posns > 5) {
+ $tics = 0.4; // e.g. 0, 0.2, 0.4, ...
+ } else if ($posns > 3) {
+ $tics = 0.5; // e.g. 0, 0.5, 1, ...
+ } else if ($posns > 2) {
+ $tics = 1; // e.g. 0, 1, 2, ...
+ } else if ($posns > 0.25) {
+ $tics = 2; // e.g. 0, 2, 4, 6
+ } else {
+ $tics = ceil($norm);
+ }
+
+ $this->tics = $tics * $this->magnitude;
+ }
+
+ /**
+ * Computes automatic boundaries on the axis
+ */
+ public function computeBoundaries() {
+ // Range
+ $this->delta = abs($this->max - $this->min);
+
+ // Check for null distribution
+ if ($this->delta == 0)
+ $this->delta = 1;
+
+ // Order of magnitude of range
+ $this->magnitude = pow(10, floor(log10($this->delta)));
+
+ $this->quantizeTics();
+
+ $this->displayMin = floor($this->min / $this->tics) * $this->tics;
+ $this->displayMax = ceil($this->max / $this->tics) * $this->tics;
+ $this->displayDelta = $this->displayMax - $this->displayMin;
+
+ // Check for null distribution
+ if ($this->displayDelta == 0) {
+ $this->displayDelta = 1;
+ }
+ }
+
+ /**
+ * Get the lower boundary on the axis3
+ *
+ * @return integer lower boundary on the axis
+ */
+ public function getLowerBoundary() {
+ return $this->displayMin;
+ }
+
+ /**
+ * Get the upper boundary on the axis3
+ *
+ * @return integer upper boundary on the axis
+ */
+ public function getUpperBoundary() {
+ return $this->displayMax;
+ }
+
+ /**
+ * Get the value between two ticks3
+ *
+ * @return integer value between two ticks
+ */
+ public function getTics() {
+ return $this->tics;
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/axis/Bound.php b/admin/inc/view/axis/Bound.php
new file mode 100644
index 0000000..3ecdb12
--- /dev/null
+++ b/admin/inc/view/axis/Bound.php
@@ -0,0 +1,156 @@
+.
+ *
+ */
+
+ /**
+ * Object representing the bounds of a dataset (its minimal and maximal values) on its vertical axis.
+ * The bounds are automatically calculated from a XYDataSet or XYSeriesDataSet.
+ * Default (calculated) bounds can be overriden using the setLowerBound() and setUpperBound() methods.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ * Created on 25 july 2007
+ */
+ class Bound {
+ /**
+ * Manually set lower bound, overrides the value calculated by computeBound().
+ */
+ private $lowerBound = null;
+
+ /**
+ * Manually set upper bound, overrides the value calculated by computeBound().
+ */
+ private $upperBound = null;
+
+ /**
+ * Computed min bound.
+ */
+ private $yMinValue = null;
+
+ /**
+ * Computed max bound.
+ */
+ private $yMaxValue = null;
+
+ /**
+ * Compute the boundaries on the axis.
+ *
+ * @param dataSet The data set
+ */
+ public function computeBound($dataSet) {
+ // Check if the data set is empty
+ $dataSetEmpty = true;
+ $serieList = null;
+ if ($dataSet instanceof XYDataSet) {
+ $pointList = $dataSet->getPointList();
+ $dataSetEmpty = count($pointList) == 0;
+
+ if (!$dataSetEmpty) {
+ // Process it as a serie
+ $serieList = array();
+ array_push($serieList, $dataSet);
+ }
+ } else if ($dataSet instanceof XYSeriesDataSet) {
+ $serieList = $dataSet->getSerieList();
+ if (count($serieList) > 0) {
+ $serie = current($serieList);
+ $dataSetEmpty = count($serie) == 0;
+ }
+ } else {
+ die("Error: unknown dataset type");
+ }
+
+ // If the dataset is empty, default some bounds
+ $yMin = 0;
+ $yMax = 1;
+ if (!$dataSetEmpty) {
+ // Compute lower and upper bound on the value axis
+ unset($yMin);
+ unset($yMax);
+
+ foreach ($serieList as $serie) {
+ foreach ($serie->getPointList() as $point) {
+ $y = $point->getY();
+
+ if (!isset($yMin)) {
+ $yMin = $y;
+ $yMax = $y;
+ } else {
+ if ($y < $yMin) {
+ $yMin = $y;
+ }
+
+ if ($y > $yMax) {
+ $yMax = $y;
+ }
+ }
+ }
+ }
+ }
+
+ // If user specified bounds and they are actually greater than computer bounds, override computed bounds
+ if (isset($this->lowerBound) && $this->lowerBound < $yMin) {
+ $this->yMinValue = $this->lowerBound;
+ } else {
+ $this->yMinValue = $yMin;
+ }
+
+ if (isset($this->upperBound) && $this->upperBound > $yMax) {
+ $this->yMaxValue = $this->upperBound;
+ } else {
+ $this->yMaxValue = $yMax;
+ }
+ }
+
+ /**
+ * Getter of yMinValue.
+ *
+ * @return min bound
+ */
+ public function getYMinValue() {
+ return $this->yMinValue;
+ }
+
+ /**
+ * Getter of yMaxValue.
+ *
+ * @return max bound
+ */
+ public function getYMaxValue() {
+ return $this->yMaxValue;
+ }
+
+ /**
+ * Set manually the lower boundary value (overrides the automatic formatting).
+ * Typical usage is to set the bars starting from zero.
+ *
+ * @param double lower boundary value
+ */
+ public function setLowerBound($lowerBound) {
+ $this->lowerBound = $lowerBound;
+ }
+
+ /**
+ * Set manually the upper boundary value (overrides the automatic formatting).
+ *
+ * @param double upper boundary value
+ */
+ public function setUpperBound($upperBound) {
+ $this->upperBound = $upperBound;
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/caption/Caption.php b/admin/inc/view/caption/Caption.php
new file mode 100644
index 0000000..7e25bd7
--- /dev/null
+++ b/admin/inc/view/caption/Caption.php
@@ -0,0 +1,112 @@
+.
+ *
+ */
+
+ /**
+ * Caption.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ * Created on 30 july 2007
+ */
+ class Caption {
+ protected $labelBoxWidth;
+ protected $labelBoxHeight;
+
+ // Plot
+ protected $plot;
+
+ // Label list
+ protected $labelList;
+
+ // Color set
+ protected $colorSet;
+
+ /**
+ * Constructor of Caption
+ */
+ public function Caption() {
+ $this->labelBoxWidth = 15;
+ $this->labelBoxHeight = 15;
+ }
+
+ /**
+ * Render the caption.
+ */
+ public function render() {
+ // Get graphical obects
+ $img = $this->plot->getImg();
+ $palette = $this->plot->getPalette();
+ $text = $this->plot->getText();
+ $primitive = $this->plot->getPrimitive();
+
+ // Get the caption area
+ $captionArea = $this->plot->getCaptionArea();
+
+ // Get the pie color set
+ $colorSet = $this->colorSet;
+ $colorSet->reset();
+
+ $i = 0;
+ foreach ($this->labelList as $label) {
+ // Get the next color
+ $color = $colorSet->currentColor();
+ $colorSet->next();
+
+ $boxX1 = $captionArea->x1;
+ $boxX2 = $boxX1 + $this->labelBoxWidth;
+ $boxY1 = $captionArea->y1 + 5 + $i * ($this->labelBoxHeight + 5);
+ $boxY2 = $boxY1 + $this->labelBoxHeight;
+
+ $primitive->outlinedBox($boxX1, $boxY1, $boxX2, $boxY2, $palette->axisColor[0], $palette->axisColor[1]);
+ imagefilledrectangle($img, $boxX1 + 2, $boxY1 + 2, $boxX2 - 2, $boxY2 - 2, $color->getColor($img));
+
+ $text->printText($img, $boxX2 + 5, $boxY1 + $this->labelBoxHeight / 2, $this->plot->getTextColor(), $label, $text->fontCondensed, $text->VERTICAL_CENTER_ALIGN);
+
+ $i++;
+ }
+ }
+
+ /**
+ * Sets the plot.
+ *
+ * @param Plot The plot
+ */
+ public function setPlot($plot) {
+ $this->plot = $plot;
+ }
+
+ /**
+ * Sets the label list.
+ *
+ * @param Array label list
+ */
+ public function setLabelList($labelList) {
+ $this->labelList = $labelList;
+ }
+
+
+ /**
+ * Sets the color set.
+ *
+ * @param Array Color set
+ */
+ public function setColorSet($colorSet) {
+ $this->colorSet = $colorSet;
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/chart/BarChart.php b/admin/inc/view/chart/BarChart.php
new file mode 100644
index 0000000..6a27549
--- /dev/null
+++ b/admin/inc/view/chart/BarChart.php
@@ -0,0 +1,183 @@
+.
+ *
+ */
+
+ /**
+ * Base abstract class for rendering both horizontal and vertical bar charts.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ */
+ abstract class BarChart extends Chart {
+ protected $bound;
+ protected $axis;
+ protected $hasSeveralSerie;
+
+ /**
+ * Creates a new bar chart.
+ *
+ * @param integer width of the image
+ * @param integer height of the image
+ */
+ protected function BarChart($width, $height) {
+ parent::Chart($width, $height);
+
+ // Initialize the bounds
+ $this->bound = new Bound();
+ $this->bound->setLowerBound(0);
+ }
+
+ /**
+ * Compute the axis.
+ */
+ protected function computeAxis() {
+ $this->axis = new Axis($this->bound->getYMinValue(), $this->bound->getYMaxValue());
+ $this->axis->computeBoundaries();
+ }
+
+ /**
+ * Create the image.
+ */
+ protected function createImage() {
+ parent::createImage();
+
+ // Get graphical obects
+ $img = $this->plot->getImg();
+ $palette = $this->plot->getPalette();
+ $text = $this->plot->getText();
+ $primitive = $this->plot->getPrimitive();
+
+ // Get the graph area
+ $graphArea = $this->plot->getGraphArea();
+
+ // Aqua-like background
+ for ($i = $graphArea->y1; $i < $graphArea->y2; $i++) {
+ $color = $palette->backgroundColor[($i + 3) % 4];
+ $primitive->line($graphArea->x1, $i, $graphArea->x2, $i, $color);
+ }
+
+ // Axis
+ imagerectangle($img, $graphArea->x1 - 1, $graphArea->y1, $graphArea->x1, $graphArea->y2, $palette->axisColor[0]->getColor($img));
+ imagerectangle($img, $graphArea->x1 - 1, $graphArea->y2, $graphArea->x2, $graphArea->y2 + 1, $palette->axisColor[0]->getColor($img));
+ }
+
+ /**
+ * Returns true if the data set has some data.
+ * @param minNumberOfPoint Minimum number of points (1 for bars, 2 for lines).
+ *
+ * @return true if data set empty
+ */
+ protected function isEmptyDataSet($minNumberOfPoint) {
+ if ($this->dataSet instanceof XYDataSet) {
+ $pointList = $this->dataSet->getPointList();
+ $pointCount = count($pointList);
+ return $pointCount < $minNumberOfPoint;
+ } else if ($this->dataSet instanceof XYSeriesDataSet) {
+ $serieList = $this->dataSet->getSerieList();
+ reset($serieList);
+ if (count($serieList) > 0) {
+ $serie = current($serieList);
+ $pointList = $serie->getPointList();
+ $pointCount = count($pointList);
+ return $pointCount < $minNumberOfPoint;
+ }
+ } else {
+ die("Error: unknown dataset type");
+ }
+ }
+
+ /**
+ * Checks the data model before rendering the graph.
+ */
+ protected function checkDataModel() {
+ // Check if a dataset was defined
+ if (!$this->dataSet) {
+ die("Error: No dataset defined.");
+ }
+
+ // Bar charts accept both XYDataSet and XYSeriesDataSet
+ if ($this->dataSet instanceof XYDataSet) {
+ // The dataset contains only one serie
+ $this->hasSeveralSerie = false;
+ } else if ($this->dataSet instanceof XYSeriesDataSet) {
+ // Check if each series has the same number of points
+ unset($lastPointCount);
+ $serieList = $this->dataSet->getSerieList();
+ for ($i = 0; $i < count($serieList); $i++) {
+ $serie = $serieList[$i];
+ $pointCount = count($serie->getPointList());
+ if (isset($lastPointCount) && $pointCount != $lastPointCount) {
+ die("Error: serie <" . $i . "> doesn't have the same number of points as last serie (last one: <" . $lastPointCount. ">, this one: <" . $pointCount. ">).");
+ }
+ $lastPointCount = $pointCount;
+ }
+
+ // The dataset contains several series
+ $this->hasSeveralSerie = true;
+ } else {
+ die("Error: Bar chart accept only XYDataSet and XYSeriesDataSet");
+ }
+ }
+
+ /**
+ * Return the data as a series list (for consistency).
+ *
+ * @return List of series
+ */
+ protected function getDataAsSerieList() {
+ // Get the data as a series list
+ $serieList = null;
+ if ($this->dataSet instanceof XYSeriesDataSet) {
+ $serieList = $this->dataSet->getSerieList();
+ } else if ($this->dataSet instanceof XYDataSet) {
+ $serieList = array();
+ array_push($serieList, $this->dataSet);
+ }
+
+ return $serieList;
+ }
+
+ /**
+ * Return the first serie of the list, or the dataSet itself if there is no serie.
+ *
+ * @return XYDataSet
+ */
+ protected function getFirstSerieOfList() {
+ $pointList = null;
+ if ($this->dataSet instanceof XYSeriesDataSet) {
+ // For a series dataset, print the legend from the first serie
+ $serieList = $this->dataSet->getSerieList();
+ reset($serieList);
+ $serie = current($serieList);
+ $pointList = $serie->getPointList();
+ } else if ($this->dataSet instanceof XYDataSet) {
+ $pointList = $this->dataSet->getPointList();
+ }
+
+ return $pointList;
+ }
+
+ /**
+ * Retourns the bound.
+ *
+ * @return bound Bound
+ */
+ public function getBound() {
+ return $this->bound;
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/chart/Chart.php b/admin/inc/view/chart/Chart.php
new file mode 100644
index 0000000..f81b9cf
--- /dev/null
+++ b/admin/inc/view/chart/Chart.php
@@ -0,0 +1,119 @@
+.
+ *
+ */
+
+ /*! \mainpage Libchart
+ *
+ * This is the reference API, automatically compiled by Doxygen.
+ * You can find here information that is not covered by the tutorial.
+ *
+ */
+
+ /**
+ * Base chart class.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ */
+ abstract class Chart {
+ /**
+ * The chart configuration.
+ */
+ protected $config;
+
+ /**
+ * The data set.
+ */
+ protected $dataSet;
+
+ /**
+ * Plot (holds graphical attributes).
+ */
+ protected $plot;
+
+ /**
+ * Abstract constructor of Chart.
+ *
+ * @param integer width of the image
+ * @param integer height of the image
+ */
+ protected function Chart($width, $height) {
+ // Initialize the configuration
+ $this->config = new ChartConfig();
+
+ // Creates the plot
+ $this->plot = new Plot($width, $height);
+ $this->plot->setTitle("Untitled chart");
+ $this->plot->setLogoFileName(dirname(__FILE__) . "/../../../images/PoweredBy.png");
+ }
+
+ /**
+ * Checks the data model before rendering the graph.
+ */
+ protected function checkDataModel() {
+ // Check if a dataset was defined
+ if (!$this->dataSet) {
+ die("Error: No dataset defined.");
+ }
+
+ // Maybe no points are defined, but that's ok. This will yield and empty graph with default boundaries.
+ }
+
+ /**
+ * Create the image.
+ */
+ protected function createImage() {
+ $this->plot->createImage();
+ }
+
+ /**
+ * Sets the data set.
+ *
+ * @param dataSet The data set
+ */
+ public function setDataSet($dataSet) {
+ $this->dataSet = $dataSet;
+ }
+
+ /**
+ * Return the chart configuration.
+ *
+ * @return configuration : ChartConfig
+ */
+ public function getConfig() {
+ return $this->config;
+ }
+
+ /**
+ * Return the plot.
+ *
+ * @return plot
+ */
+ public function getPlot() {
+ return $this->plot;
+ }
+
+ /**
+ * Sets the title.
+ *
+ * @param string New title
+ */
+ public function setTitle($title) {
+ $this->plot->setTitle($title);
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/chart/HorizontalBarChart.php b/admin/inc/view/chart/HorizontalBarChart.php
new file mode 100644
index 0000000..25cbb2c
--- /dev/null
+++ b/admin/inc/view/chart/HorizontalBarChart.php
@@ -0,0 +1,230 @@
+.
+ *
+ */
+
+ /**
+ * Horizontal bar chart
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ */
+ class HorizontalBarChart extends BarChart {
+ /**
+ * Ratio of empty space beside the bars.
+ */
+ private $emptyToFullRatio;
+
+ /**
+ * Creates a new horizontal bar chart.
+ *
+ * @param integer width of the image
+ * @param integer height of the image
+ */
+ public function HorizontalBarChart($width = 600, $height = 250) {
+ parent::BarChart($width, $height);
+
+ $this->emptyToFullRatio = 1 / 5;
+ $this->plot->setGraphPadding(new Padding(5, 30, 30, 50));
+ }
+
+ /**
+ * Computes the layout.
+ */
+ protected function computeLayout() {
+ if ($this->hasSeveralSerie) {
+ $this->plot->setHasCaption(true);
+ }
+ $this->plot->computeLayout();
+ }
+
+ /**
+ * Print the axis.
+ */
+ protected function printAxis() {
+ $minValue = $this->axis->getLowerBoundary();
+ $maxValue = $this->axis->getUpperBoundary();
+ $stepValue = $this->axis->getTics();
+
+ // Get graphical obects
+ $img = $this->plot->getImg();
+ $palette = $this->plot->getPalette();
+ $text = $this->plot->getText();
+
+ // Get the graph area
+ $graphArea = $this->plot->getGraphArea();
+
+ // Horizontal axis
+ for ($value = $minValue; $value <= $maxValue; $value += $stepValue) {
+ $x = $graphArea->x1 + ($value - $minValue) * ($graphArea->x2 - $graphArea->x1) / ($this->axis->displayDelta);
+
+ imagerectangle($img, $x - 1, $graphArea->y2 + 2, $x, $graphArea->y2 + 3, $palette->axisColor[0]->getColor($img));
+ imagerectangle($img, $x - 1, $graphArea->y2, $x, $graphArea->y2 + 1, $palette->axisColor[1]->getColor($img));
+
+ $text->printText($img, $x, $graphArea->y2 + 5, $this->plot->getTextColor(), $value, $text->fontCondensed, $text->HORIZONTAL_CENTER_ALIGN);
+ }
+
+ // Get first serie of a list
+ $pointList = $this->getFirstSerieOfList();
+
+ // Vertical Axis
+ $pointCount = count($pointList);
+ reset($pointList);
+ $rowHeight = ($graphArea->y2 - $graphArea->y1) / $pointCount;
+ reset($pointList);
+ for ($i = 0; $i <= $pointCount; $i++) {
+ $y = $graphArea->y2 - $i * $rowHeight;
+
+ imagerectangle($img, $graphArea->x1 - 3, $y, $graphArea->x1 - 2, $y + 1, $palette->axisColor[0]->getColor($img));
+ imagerectangle($img, $graphArea->x1 - 1, $y, $graphArea->x1, $y + 1, $palette->axisColor[1]->getColor($img));
+
+ if ($i < $pointCount) {
+ $point = current($pointList);
+ next($pointList);
+
+ $label = $point->getX();
+
+ $text->printText($img, $graphArea->x1 - 5, $y - $rowHeight / 2, $this->plot->getTextColor(), $label, $text->fontCondensed, $text->HORIZONTAL_RIGHT_ALIGN | $text->VERTICAL_CENTER_ALIGN);
+ }
+ }
+ }
+
+ /**
+ * Print the bars.
+ */
+ protected function printBar() {
+ // Get the data as a list of series for consistency
+ $serieList = $this->getDataAsSerieList();
+
+ // Get graphical obects
+ $img = $this->plot->getImg();
+ $palette = $this->plot->getPalette();
+ $text = $this->plot->getText();
+
+ // Get the graph area
+ $graphArea = $this->plot->getGraphArea();
+
+ $minValue = $this->axis->getLowerBoundary();
+ $maxValue = $this->axis->getUpperBoundary();
+ $stepValue = $this->axis->getTics();
+
+ // Start from the first color for the first serie
+ $barColorSet = $palette->barColorSet;
+ $barColorSet->reset();
+
+ $serieCount = count($serieList);
+ for ($j = 0; $j < $serieCount; $j++) {
+ $serie = $serieList[$j];
+ $pointList = $serie->getPointList();
+ $pointCount = count($pointList);
+ reset($pointList);
+
+ // Select the next color for the next serie
+ if (!$this->config->getUseMultipleColor()) {
+ $color = $barColorSet->currentColor();
+ $shadowColor = $barColorSet->currentShadowColor();
+ $barColorSet->next();
+ }
+
+ $rowHeight = ($graphArea->y2 - $graphArea->y1) / $pointCount;
+ for ($i = 0; $i < $pointCount; $i++) {
+ $y = $graphArea->y2 - $i * $rowHeight;
+
+ $point = current($pointList);
+ next($pointList);
+
+ $value = $point->getY();
+
+ $xmax = $graphArea->x1 + ($value - $minValue) * ($graphArea->x2 - $graphArea->x1) / ($this->axis->displayDelta);
+
+ // Bar dimensions
+ $yWithMargin = $y - $rowHeight * $this->emptyToFullRatio;
+ $rowWidthWithMargin = $rowHeight * (1 - $this->emptyToFullRatio * 2);
+ $barWidth = $rowWidthWithMargin / $serieCount;
+ $barOffset = $barWidth * $j;
+ $y1 = $yWithMargin - $barWidth - $barOffset;
+ $y2 = $yWithMargin - $barOffset - 1;
+
+ // Select the next color for the next item in the serie
+ if ($this->config->getUseMultipleColor()) {
+ $color = $barColorSet->currentColor();
+ $shadowColor = $barColorSet->currentShadowColor();
+ $barColorSet->next();
+ }
+
+ // Draw caption text on bar
+ if ($this->config->getShowPointCaption()) {
+ $text->printText($img, $xmax + 5, $y2 - $barWidth / 2, $this->plot->getTextColor(), $value, $text->fontCondensed, $text->VERTICAL_CENTER_ALIGN);
+ }
+
+ // Draw the horizontal bar
+ imagefilledrectangle($img, $graphArea->x1 + 1, $y1, $xmax, $y2, $shadowColor->getColor($img));
+
+ // Prevents drawing a small box when x = 0
+ if ($graphArea->x1 != $xmax) {
+ imagefilledrectangle($img, $graphArea->x1 + 2, $y1 + 1, $xmax - 4, $y2, $color->getColor($img));
+ }
+ }
+ }
+ }
+
+ /**
+ * Renders the caption.
+ */
+ protected function printCaption() {
+ // Get the list of labels
+ $labelList = $this->dataSet->getTitleList();
+
+ // Create the caption
+ $caption = new Caption();
+ $caption->setPlot($this->plot);
+ $caption->setLabelList($labelList);
+
+ $palette = $this->plot->getPalette();
+ $barColorSet = $palette->barColorSet;
+ $caption->setColorSet($barColorSet);
+
+ // Render the caption
+ $caption->render();
+ }
+
+ /**
+ * Render the chart image.
+ *
+ * @param string name of the file to render the image to (optional)
+ */
+ public function render($fileName = null) {
+ // Check the data model
+ $this->checkDataModel();
+
+ $this->bound->computeBound($this->dataSet);
+ $this->computeAxis();
+ $this->computeLayout();
+ $this->createImage();
+ $this->plot->printLogo();
+ $this->plot->printTitle();
+ if (!$this->isEmptyDataSet(1)) {
+ $this->printAxis();
+ $this->printBar();
+ if ($this->hasSeveralSerie) {
+ $this->printCaption();
+ }
+ }
+
+ $this->plot->render($fileName);
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/chart/LineChart.php b/admin/inc/view/chart/LineChart.php
new file mode 100644
index 0000000..382e1b1
--- /dev/null
+++ b/admin/inc/view/chart/LineChart.php
@@ -0,0 +1,200 @@
+.
+ *
+ */
+
+ /**
+ * Line chart.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ */
+ class LineChart extends BarChart {
+ /**
+ * Creates a new line chart.
+ * Line charts allow for XYDataSet and XYSeriesDataSet in order to plot several lines.
+ *
+ * @param integer width of the image
+ * @param integer height of the image
+ */
+ public function LineChart($width = 600, $height = 250) {
+ parent::BarChart($width, $height);
+
+ $this->plot->setGraphPadding(new Padding(5, 30, 50, 50));
+ }
+
+ /**
+ * Computes the layout.
+ */
+ protected function computeLayout() {
+ if ($this->hasSeveralSerie) {
+ $this->plot->setHasCaption(true);
+ }
+ $this->plot->computeLayout();
+ }
+
+ /**
+ * Print the axis.
+ */
+ protected function printAxis() {
+ $minValue = $this->axis->getLowerBoundary();
+ $maxValue = $this->axis->getUpperBoundary();
+ $stepValue = $this->axis->getTics();
+
+ // Get graphical obects
+ $img = $this->plot->getImg();
+ $palette = $this->plot->getPalette();
+ $text = $this->plot->getText();
+
+ // Get the graph area
+ $graphArea = $this->plot->getGraphArea();
+
+ // Vertical axis
+ for ($value = $minValue; $value <= $maxValue; $value += $stepValue) {
+ $y = $graphArea->y2 - ($value - $minValue) * ($graphArea->y2 - $graphArea->y1) / ($this->axis->displayDelta);
+
+ imagerectangle($img, $graphArea->x1 - 3, $y, $graphArea->x1 - 2, $y + 1, $palette->axisColor[0]->getColor($img));
+ imagerectangle($img, $graphArea->x1 - 1, $y, $graphArea->x1, $y + 1, $palette->axisColor[1]->getColor($img));
+
+ $text->printText($img, $graphArea->x1 - 5, $y, $this->plot->getTextColor(), $value, $text->fontCondensed, $text->HORIZONTAL_RIGHT_ALIGN | $text->VERTICAL_CENTER_ALIGN);
+ }
+
+ // Get first serie of a list
+ $pointList = $this->getFirstSerieOfList();
+
+ // Horizontal Axis
+ $pointCount = count($pointList);
+ reset($pointList);
+ $columnWidth = ($graphArea->x2 - $graphArea->x1) / ($pointCount - 1);
+
+ for ($i = 0; $i < $pointCount; $i++) {
+ $x = $graphArea->x1 + $i * $columnWidth;
+
+ imagerectangle($img, $x - 1, $graphArea->y2 + 2, $x, $graphArea->y2 + 3, $palette->axisColor[0]->getColor($img));
+ imagerectangle($img, $x - 1, $graphArea->y2, $x, $graphArea->y2 + 1, $palette->axisColor[1]->getColor($img));
+
+ $point = current($pointList);
+ next($pointList);
+
+ $label = $point->getX();
+
+ $text->printDiagonal($img, $x - 5, $graphArea->y2 + 10, $this->plot->getTextColor(), $label);
+ }
+ }
+
+ /**
+ * Print the lines.
+ */
+ protected function printLine() {
+ $minValue = $this->axis->getLowerBoundary();
+ $maxValue = $this->axis->getUpperBoundary();
+
+ // Get the data as a list of series for consistency
+ $serieList = $this->getDataAsSerieList();
+
+ // Get graphical obects
+ $img = $this->plot->getImg();
+ $palette = $this->plot->getPalette();
+ $text = $this->plot->getText();
+ $primitive = $this->plot->getPrimitive();
+
+ // Get the graph area
+ $graphArea = $this->plot->getGraphArea();
+
+ $lineColorSet = $palette->lineColorSet;
+ $lineColorSet->reset();
+ for ($j = 0; $j < count($serieList); $j++) {
+ $serie = $serieList[$j];
+ $pointList = $serie->getPointList();
+ $pointCount = count($pointList);
+ reset($pointList);
+
+ $columnWidth = ($graphArea->x2 - $graphArea->x1) / ($pointCount - 1);
+
+ $lineColor = $lineColorSet->currentColor();
+ $lineColorShadow = $lineColorSet->currentShadowColor();
+ $lineColorSet->next();
+ $x1 = null;
+ $y1 = null;
+ for ($i = 0; $i < $pointCount; $i++) {
+ $x2 = $graphArea->x1 + $i * $columnWidth;
+
+ $point = current($pointList);
+ next($pointList);
+
+ $value = $point->getY();
+
+ $y2 = $graphArea->y2 - ($value - $minValue) * ($graphArea->y2 - $graphArea->y1) / ($this->axis->displayDelta);
+
+ // Draw line
+ if ($x1) {
+ $primitive->line($x1, $y1, $x2, $y2, $lineColor, 4);
+ $primitive->line($x1, $y1 - 1, $x2, $y2 - 1, $lineColorShadow, 2);
+ }
+
+ $x1 = $x2;
+ $y1 = $y2;
+ }
+ }
+ }
+
+ /**
+ * Renders the caption.
+ */
+ protected function printCaption() {
+ // Get the list of labels
+ $labelList = $this->dataSet->getTitleList();
+
+ // Create the caption
+ $caption = new Caption();
+ $caption->setPlot($this->plot);
+ $caption->setLabelList($labelList);
+
+ $palette = $this->plot->getPalette();
+ $lineColorSet = $palette->lineColorSet;
+ $caption->setColorSet($lineColorSet);
+
+ // Render the caption
+ $caption->render();
+ }
+
+ /**
+ * Render the chart image.
+ *
+ * @param string name of the file to render the image to (optional)
+ */
+ public function render($fileName = null) {
+ // Check the data model
+ $this->checkDataModel();
+
+ $this->bound->computeBound($this->dataSet);
+ $this->computeAxis();
+ $this->computeLayout();
+ $this->createImage();
+ $this->plot->printLogo();
+ $this->plot->printTitle();
+ if (!$this->isEmptyDataSet(2)) {
+ $this->printAxis();
+ $this->printLine();
+ if ($this->hasSeveralSerie) {
+ $this->printCaption();
+ }
+ }
+
+ $this->plot->render($fileName);
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/chart/PieChart.php b/admin/inc/view/chart/PieChart.php
new file mode 100644
index 0000000..9175f71
--- /dev/null
+++ b/admin/inc/view/chart/PieChart.php
@@ -0,0 +1,266 @@
+.
+ *
+ */
+
+ /**
+ * Pie chart.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ */
+ class PieChart extends Chart {
+ protected $pieCenterX;
+ protected $pieCenterY;
+
+ /**
+ * Constructor of a pie chart.
+ *
+ * @param integer width of the image
+ * @param integer height of the image
+ */
+ public function PieChart($width = 600, $height = 250) {
+ parent::Chart($width, $height);
+ $this->plot->setGraphPadding(new Padding(15, 10, 30, 30));
+ }
+
+ /**
+ * Computes the layout.
+ */
+ protected function computeLayout() {
+ $this->plot->setHasCaption(true);
+ $this->plot->computeLayout();
+
+ // Get the graph area
+ $graphArea = $this->plot->getGraphArea();
+
+ // Compute the coordinates of the pie
+ $this->pieCenterX = $graphArea->x1 + ($graphArea->x2 - $graphArea->x1) / 2;
+ $this->pieCenterY = $graphArea->y1 + ($graphArea->y2 - $graphArea->y1) / 2;
+
+ $this->pieWidth = round(($graphArea->x2 - $graphArea->x1) * 4 / 5);
+ $this->pieHeight = round(($graphArea->y2 - $graphArea->y1) * 3.7 / 5);
+ $this->pieDepth = round($this->pieWidth * 0.05);
+ }
+
+ /**
+ * Compare two sampling point values, order from biggest to lowest value.
+ *
+ * @param double first value
+ * @param double second value
+ * @return integer result of the comparison
+ */
+ protected function sortPie($v1, $v2) {
+ return $v1[0] == $v2[0] ? 0 :
+ $v1[0] > $v2[0] ? -1 :
+ 1;
+ }
+
+ /**
+ * Compute pie values in percentage and sort them.
+ */
+ protected function computePercent() {
+ $this->total = 0;
+ $this->percent = array();
+
+ $pointList = $this->dataSet->getPointList();
+ foreach ($pointList as $point) {
+ $this->total += $point->getY();
+ }
+
+ foreach ($pointList as $point) {
+ $percent = $this->total == 0 ? 0 : 100 * $point->getY() / $this->total;
+
+ array_push($this->percent, array($percent, $point));
+ }
+
+ // Sort data points
+ if ($this->config->getSortDataPoint()) {
+ usort($this->percent, array("PieChart", "sortPie"));
+ }
+ }
+
+ /**
+ * Creates the pie chart image.
+ */
+ protected function createImage() {
+ parent::createImage();
+
+ // Get graphical obects
+ $img = $this->plot->getImg();
+ $palette = $this->plot->getPalette();
+ $primitive = $this->plot->getPrimitive();
+
+ // Get the graph area
+ $graphArea = $this->plot->getGraphArea();
+
+ // Legend box
+ $primitive->outlinedBox($graphArea->x1, $graphArea->y1, $graphArea->x2, $graphArea->y2, $palette->axisColor[0], $palette->axisColor[1]);
+
+ // Aqua-like background
+ for ($i = $graphArea->y1 + 2; $i < $graphArea->y2 - 1; $i++) {
+ $color = $palette->backgroundColor[($i + 3) % 4];
+ $primitive->line($graphArea->x1 + 2, $i, $graphArea->x2 - 2, $i, $color);
+ }
+ }
+
+ /**
+ * Renders the caption.
+ */
+ protected function printCaption() {
+ // Create a list of labels
+ $labelList = array();
+ foreach($this->percent as $percent) {
+ list($percent, $point) = $percent;
+ $label = $point->getX();
+
+ array_push($labelList, $label);
+ }
+
+ // Create the caption
+ $caption = new Caption();
+ $caption->setPlot($this->plot);
+ $caption->setLabelList($labelList);
+
+ $palette = $this->plot->getPalette();
+ $pieColorSet = $palette->pieColorSet;
+ $caption->setColorSet($pieColorSet);
+
+ // Render the caption
+ $caption->render();
+ }
+
+ /**
+ * Draw a 2D disc.
+ *
+ * @param integer Center coordinate (y)
+ * @param array Colors for each portion
+ * @param bitfield Drawing mode
+ */
+ protected function drawDisc($cy, $colorArray, $mode) {
+ // Get graphical obects
+ $img = $this->plot->getImg();
+
+ $i = 0;
+ $oldAngle = 0;
+ $percentTotal = 0;
+
+ foreach ($this->percent as $a) {
+ list ($percent, $point) = $a;
+
+ // If value is null, don't draw this arc
+ if ($percent <= 0) {
+ continue;
+ }
+
+ $color = $colorArray[$i % count($colorArray)];
+
+ $percentTotal += $percent;
+ $newAngle = $percentTotal * 360 / 100;
+
+ // imagefilledarc doesn't like null values (#1)
+ if ($newAngle - $oldAngle >= 1) {
+ imagefilledarc($img, $this->pieCenterX, $cy, $this->pieWidth, $this->pieHeight, $oldAngle, $newAngle, $color->getColor($img), $mode);
+ }
+
+ $oldAngle = $newAngle;
+
+ $i++;
+ }
+ }
+
+ /**
+ * Print the percentage text.
+ */
+ protected function drawPercent() {
+ // Get graphical obects
+ $img = $this->plot->getImg();
+ $palette = $this->plot->getPalette();
+ $text = $this->plot->getText();
+ $primitive = $this->plot->getPrimitive();
+
+ $angle1 = 0;
+ $percentTotal = 0;
+
+ foreach ($this->percent as $a) {
+ list ($percent, $point) = $a;
+
+ // If value is null, the arc isn't drawn, no need to display percent
+ if ($percent <= 0) {
+ continue;
+ }
+
+ $percentTotal += $percent;
+ $angle2 = $percentTotal * 2 * M_PI / 100;
+
+ $angle = $angle1 + ($angle2 - $angle1) / 2;
+ $label = number_format($percent) . "%";
+
+ $x = cos($angle) * ($this->pieWidth + 35) / 2 + $this->pieCenterX;
+ $y = sin($angle) * ($this->pieHeight + 35) / 2 + $this->pieCenterY;
+
+ $text->printText($img, $x, $y, $this->plot->getTextColor(), $label, $text->fontCondensed, $text->HORIZONTAL_CENTER_ALIGN | $text->VERTICAL_CENTER_ALIGN);
+
+ $angle1 = $angle2;
+ }
+ }
+
+ /**
+ * Print the pie chart.
+ */
+ protected function printPie() {
+ // Get graphical obects
+ $img = $this->plot->getImg();
+ $palette = $this->plot->getPalette();
+ $text = $this->plot->getText();
+ $primitive = $this->plot->getPrimitive();
+
+ // Get the pie color set
+ $pieColorSet = $palette->pieColorSet;
+ $pieColorSet->reset();
+
+ // Silhouette
+ for ($cy = $this->pieCenterY + $this->pieDepth / 2; $cy >= $this->pieCenterY - $this->pieDepth / 2; $cy--) {
+ $this->drawDisc($cy, $palette->pieColorSet->shadowColorList, IMG_ARC_EDGED);
+ }
+
+ // Top
+ $this->drawDisc($this->pieCenterY - $this->pieDepth / 2, $palette->pieColorSet->colorList, IMG_ARC_PIE);
+
+ // Top Outline
+ if ($this->config->getShowPointCaption()) {
+ $this->drawPercent();
+ }
+ }
+
+ /**
+ * Render the chart image.
+ *
+ * @param string name of the file to render the image to (optional)
+ */
+ public function render($fileName = null) {
+ $this->computePercent();
+ $this->computeLayout();
+ $this->createImage();
+ $this->plot->printLogo();
+ $this->plot->printTitle();
+ $this->printPie();
+ $this->printCaption();
+
+ $this->plot->render($fileName);
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/chart/VerticalBarChart.php b/admin/inc/view/chart/VerticalBarChart.php
new file mode 100644
index 0000000..66cae77
--- /dev/null
+++ b/admin/inc/view/chart/VerticalBarChart.php
@@ -0,0 +1,229 @@
+.
+ *
+ */
+
+ /**
+ * Chart composed of vertical bars.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ */
+ class VerticalBarChart extends BarChart {
+ /**
+ * Ratio of empty space beside the bars.
+ */
+ private $emptyToFullRatio;
+
+ /**
+ * Creates a new vertical bar chart
+ *
+ * @param integer width of the image
+ * @param integer height of the image
+ */
+ public function VerticalBarChart($width = 600, $height = 250) {
+ parent::BarChart($width, $height);
+
+ $this->emptyToFullRatio = 1 / 5;
+ $this->plot->setGraphPadding(new Padding(5, 30, 50, 50));
+ }
+
+ /**
+ * Computes the layout.
+ */
+ protected function computeLayout() {
+ if ($this->hasSeveralSerie) {
+ $this->plot->setHasCaption(true);
+ }
+ $this->plot->computeLayout();
+ }
+
+ /**
+ * Print the horizontal and veritcal axis.
+ */
+ protected function printAxis() {
+ $minValue = $this->axis->getLowerBoundary();
+ $maxValue = $this->axis->getUpperBoundary();
+ $stepValue = $this->axis->getTics();
+
+ // Get graphical obects
+ $img = $this->plot->getImg();
+ $palette = $this->plot->getPalette();
+ $text = $this->plot->getText();
+
+ // Get the graph area
+ $graphArea = $this->plot->getGraphArea();
+
+ // Vertical axis
+ for ($value = $minValue; $value <= $maxValue; $value += $stepValue) {
+ $y = $graphArea->y2 - ($value - $minValue) * ($graphArea->y2 - $graphArea->y1) / ($this->axis->displayDelta);
+
+ imagerectangle($img, $graphArea->x1 - 3, $y, $graphArea->x1 - 2, $y + 1, $palette->axisColor[0]->getColor($img));
+ imagerectangle($img, $graphArea->x1 - 1, $y, $graphArea->x1, $y + 1, $palette->axisColor[1]->getColor($img));
+
+ $text->printText($img, $graphArea->x1 - 5, $y, $this->plot->getTextColor(), $value, $text->fontCondensed, $text->HORIZONTAL_RIGHT_ALIGN | $text->VERTICAL_CENTER_ALIGN);
+ }
+
+ // Get first serie of a list
+ $pointList = $this->getFirstSerieOfList();
+
+ // Horizontal Axis
+ $pointCount = count($pointList);
+ reset($pointList);
+ $columnWidth = ($graphArea->x2 - $graphArea->x1) / $pointCount;
+ for ($i = 0; $i <= $pointCount; $i++) {
+ $x = $graphArea->x1 + $i * $columnWidth;
+
+ imagerectangle($img, $x - 1, $graphArea->y2 + 2, $x, $graphArea->y2 + 3, $palette->axisColor[0]->getColor($img));
+ imagerectangle($img, $x - 1, $graphArea->y2, $x, $graphArea->y2 + 1, $palette->axisColor[1]->getColor($img));
+
+ if ($i < $pointCount) {
+ $point = current($pointList);
+ next($pointList);
+
+ $label = $point->getX();
+
+ $text->printDiagonal($img, $x + $columnWidth * 1 / 3, $graphArea->y2 + 10, $this->plot->getTextColor(), $label);
+ }
+ }
+ }
+
+ /**
+ * Print the bars.
+ */
+ protected function printBar() {
+ // Get the data as a list of series for consistency
+ $serieList = $this->getDataAsSerieList();
+
+ // Get graphical obects
+ $img = $this->plot->getImg();
+ $palette = $this->plot->getPalette();
+ $text = $this->plot->getText();
+
+ // Get the graph area
+ $graphArea = $this->plot->getGraphArea();
+
+ // Start from the first color for the first serie
+ $barColorSet = $palette->barColorSet;
+ $barColorSet->reset();
+
+ $minValue = $this->axis->getLowerBoundary();
+ $maxValue = $this->axis->getUpperBoundary();
+ $stepValue = $this->axis->getTics();
+
+ $serieCount = count($serieList);
+ for ($j = 0; $j < $serieCount; $j++) {
+ $serie = $serieList[$j];
+ $pointList = $serie->getPointList();
+ $pointCount = count($pointList);
+ reset($pointList);
+
+ // Select the next color for the next serie
+ if (!$this->config->getUseMultipleColor()) {
+ $color = $barColorSet->currentColor();
+ $shadowColor = $barColorSet->currentShadowColor();
+ $barColorSet->next();
+ }
+
+ $columnWidth = ($graphArea->x2 - $graphArea->x1) / $pointCount;
+ for ($i = 0; $i < $pointCount; $i++) {
+ $x = $graphArea->x1 + $i * $columnWidth;
+
+ $point = current($pointList);
+ next($pointList);
+
+ $value = $point->getY();
+
+ $ymin = $graphArea->y2 - ($value - $minValue) * ($graphArea->y2 - $graphArea->y1) / ($this->axis->displayDelta);
+
+ // Bar dimensions
+ $xWithMargin = $x + $columnWidth * $this->emptyToFullRatio;
+ $columnWidthWithMargin = $columnWidth * (1 - $this->emptyToFullRatio * 2);
+ $barWidth = $columnWidthWithMargin / $serieCount;
+ $barOffset = $barWidth * $j;
+ $x1 = $xWithMargin + $barOffset;
+ $x2 = $xWithMargin + $barWidth + $barOffset - 1;
+
+ // Select the next color for the next item in the serie
+ if ($this->config->getUseMultipleColor()) {
+ $color = $barColorSet->currentColor();
+ $shadowColor = $barColorSet->currentShadowColor();
+ $barColorSet->next();
+ }
+
+ // Draw caption text on bar
+ if ($this->config->getShowPointCaption()) {
+ $text->printText($img, $x1 + $barWidth / 2 , $ymin - 5, $this->plot->getTextColor(), $value, $text->fontCondensed, $text->HORIZONTAL_CENTER_ALIGN | $text->VERTICAL_BOTTOM_ALIGN);
+ }
+
+ // Draw the vertical bar
+ imagefilledrectangle($img, $x1, $ymin, $x2, $graphArea->y2 - 1, $shadowColor->getColor($img));
+
+ // Prevents drawing a small box when y = 0
+ if ($ymin != $graphArea->y2) {
+ imagefilledrectangle($img, $x1 + 1, $ymin + 1, $x2 - 4, $graphArea->y2 - 1, $color->getColor($img));
+ }
+ }
+ }
+ }
+
+ /**
+ * Renders the caption.
+ */
+ protected function printCaption() {
+ // Get the list of labels
+ $labelList = $this->dataSet->getTitleList();
+
+ // Create the caption
+ $caption = new Caption();
+ $caption->setPlot($this->plot);
+ $caption->setLabelList($labelList);
+
+ $palette = $this->plot->getPalette();
+ $barColorSet = $palette->barColorSet;
+ $caption->setColorSet($barColorSet);
+
+ // Render the caption
+ $caption->render();
+ }
+
+ /**
+ * Render the chart image.
+ *
+ * @param string name of the file to render the image to (optional)
+ */
+ public function render($fileName = null) {
+ // Check the data model
+ $this->checkDataModel();
+
+ $this->bound->computeBound($this->dataSet);
+ $this->computeAxis();
+ $this->computeLayout();
+ $this->createImage();
+ $this->plot->printLogo();
+ $this->plot->printTitle();
+ if (!$this->isEmptyDataSet(1)) {
+ $this->printAxis();
+ $this->printBar();
+ if ($this->hasSeveralSerie) {
+ $this->printCaption();
+ }
+ }
+
+ $this->plot->render($fileName);
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/color/Color.php b/admin/inc/view/color/Color.php
new file mode 100644
index 0000000..01bdfd1
--- /dev/null
+++ b/admin/inc/view/color/Color.php
@@ -0,0 +1,99 @@
+.
+ *
+ */
+
+ /**
+ * Color.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ */
+ class Color {
+ private $red;
+ private $green;
+ private $blue;
+ private $alpha;
+ private $gdColor;
+
+ /**
+ * Creates a new color
+ *
+ * @param integer red [0..255]
+ * @param integer green [0..255]
+ * @param integer blue [0..255]
+ * @param integer alpha [0..255]
+ */
+ public function Color($red, $green, $blue, $alpha = 0) {
+ $this->red = (int) $red;
+ $this->green = (int) $green;
+ $this->blue = (int) $blue;
+ $this->alpha = (int) round($alpha * 127.0 / 255);
+
+ $this->gdColor = null;
+ }
+
+ /**
+ * Get GD color.
+ *
+ * @param $img GD image resource
+ */
+ public function getColor($img) {
+ // Checks if color has already been allocated
+ if (!$this->gdColor) {
+ if ($this->alpha == 0 || !function_exists('imagecolorallocatealpha')) {
+ $this->gdColor = imagecolorallocate($img, $this->red, $this->green, $this->blue);
+ } else {
+ $this->gdColor = imagecolorallocatealpha($img, $this->red, $this->green, $this->blue, $this->alpha);
+ }
+ }
+
+ // Returns GD color
+ return $this->gdColor;
+ }
+
+ /**
+ * Clip a color component in the interval [0..255]
+ *
+ * @param integer Component
+ * @return Clipped component
+ */
+ public function clip($component) {
+ if ($component < 0) {
+ $component = 0;
+ } else if ($component > 255) {
+ $component = 255;
+ }
+
+ return $component;
+ }
+
+ /**
+ * Return a new color, which is a shadow of this one.
+ *
+ * @param double Multiplication factor
+ * @return Shadow color
+ */
+ public function getShadowColor($shadowFactor) {
+ $red = $this->clip($this->red * $shadowFactor);
+ $green = $this->clip($this->green * $shadowFactor);
+ $blue = $this->clip($this->blue * $shadowFactor);
+ $shadowColor = new Color($red, $green, $blue);
+
+ return $shadowColor;
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/color/ColorSet.php b/admin/inc/view/color/ColorSet.php
new file mode 100644
index 0000000..e2c7b51
--- /dev/null
+++ b/admin/inc/view/color/ColorSet.php
@@ -0,0 +1,88 @@
+.
+ *
+ */
+
+ /**
+ * A set of colors, used for drawing series of data.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ * Created on 26 july 2007
+ */
+ class ColorSet {
+ public $colorList;
+ public $shadowColorList;
+
+ /**
+ * ColorSet constructor.
+ *
+ * @param $shadowFactor Shadow factor
+ * @param $colorArray Colors as an array
+ */
+ public function ColorSet($colorList, $shadowFactor) {
+ $this->colorList = $colorList;
+ $this->shadowColorList = array();
+
+ // Generate the shadow color set
+ foreach ($colorList as $color) {
+ $shadowColor = $color->getShadowColor($shadowFactor);
+
+ array_push($this->shadowColorList, $shadowColor);
+ }
+ }
+
+ /**
+ * Reset the iterator over the collections of colors.
+ */
+ public function reset() {
+ reset($this->colorList);
+ reset($this->shadowColorList);
+ }
+
+ /**
+ * Iterate over the colors and shadow colors. When we go after the last one, loop over.
+ *
+ */
+ public function next() {
+ $value = next($this->colorList);
+ next($this->shadowColorList);
+
+ // When we go after the last value, loop over.
+ if ($value == FALSE) {
+ $this->reset();
+ }
+ }
+
+ /**
+ * Returns the current color.
+ *
+ * @return Current color
+ */
+ public function currentColor() {
+ return current($this->colorList);
+ }
+
+ /**
+ * Returns the current shadow color.
+ *
+ * @return Current shadow color
+ */
+ public function currentShadowColor() {
+ return current($this->shadowColorList);
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/color/Palette.php b/admin/inc/view/color/Palette.php
new file mode 100644
index 0000000..0b960f7
--- /dev/null
+++ b/admin/inc/view/color/Palette.php
@@ -0,0 +1,156 @@
+.
+ *
+ */
+
+ /**
+ * Color palette shared by all chart types.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ * Created on 25 july 2007
+ */
+ class Palette {
+ // Plot attributes
+ public $red;
+ public $axisColor;
+ public $backgroundColor;
+
+ // Specific chart attributes
+ public $barColorSet;
+ public $lineColorSet;
+ public $pieColorSet;
+
+ /**
+ * Palette constructor.
+ */
+ public function Palette() {
+ $this->red = new Color(255, 0, 0);
+
+ // Set the colors for the horizontal and vertical axis
+ $this->setAxisColor(array(
+ new Color(201, 201, 201),
+ new Color(158, 158, 158)
+ ));
+
+ // Set the colors for the background
+ $this->setBackgroundColor(array(
+ new Color(242, 242, 242),
+ new Color(231, 231, 231),
+ new Color(239, 239, 239),
+ new Color(253, 253, 253)
+ ));
+
+ // Set the colors for the bars
+ $this->setBarColor(array(
+ new Color(42, 71, 181),
+ new Color(243, 198, 118),
+ new Color(128, 63, 35),
+ new Color(195, 45, 28),
+ new Color(224, 198, 165),
+ new Color(239, 238, 218),
+ new Color(40, 72, 59),
+ new Color(71, 112, 132),
+ new Color(167, 192, 199),
+ new Color(218, 233, 202)
+ ));
+
+ // Set the colors for the lines
+ $this->setLineColor(array(
+ new Color(172, 172, 210),
+ new Color(2, 78, 0),
+ new Color(148, 170, 36),
+ new Color(233, 191, 49),
+ new Color(240, 127, 41),
+ new Color(243, 63, 34),
+ new Color(190, 71, 47),
+ new Color(135, 81, 60),
+ new Color(128, 78, 162),
+ new Color(121, 75, 255),
+ new Color(142, 165, 250),
+ new Color(162, 254, 239),
+ new Color(137, 240, 166),
+ new Color(104, 221, 71),
+ new Color(98, 174, 35),
+ new Color(93, 129, 1)
+ ));
+
+ // Set the colors for the pie
+ $this->setPieColor(array(
+ new Color(2, 78, 0),
+ new Color(148, 170, 36),
+ new Color(233, 191, 49),
+ new Color(240, 127, 41),
+ new Color(243, 63, 34),
+ new Color(190, 71, 47),
+ new Color(135, 81, 60),
+ new Color(128, 78, 162),
+ new Color(121, 75, 255),
+ new Color(142, 165, 250),
+ new Color(162, 254, 239),
+ new Color(137, 240, 166),
+ new Color(104, 221, 71),
+ new Color(98, 174, 35),
+ new Color(93, 129, 1)
+ ));
+ }
+
+ /**
+ * Set the colors for the axis.
+ *
+ * @param colors Array of Color
+ */
+ public function setAxisColor($colors) {
+ $this->axisColor = $colors;
+ }
+
+ /**
+ * Set the colors for the background.
+ *
+ * @param colors Array of Color
+ */
+ public function setBackgroundColor($colors) {
+ $this->backgroundColor = $colors;
+ }
+
+ /**
+ * Set the colors for the bar charts.
+ *
+ * @param colors Array of Color
+ */
+ public function setBarColor($colors) {
+ $this->barColorSet = new ColorSet($colors, 0.75);
+ }
+
+ /**
+ * Set the colors for the line charts.
+ *
+ * @param colors Array of Color
+ */
+ public function setLineColor($colors) {
+ $this->lineColorSet = new ColorSet($colors, 0.75);
+ }
+
+ /**
+ * Set the colors for the pie charts.
+ *
+ * @param colors Array of Color
+ */
+ public function setPieColor($colors) {
+ $this->pieColorSet = new ColorSet($colors, 0.7);
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/plot/Plot.php b/admin/inc/view/plot/Plot.php
new file mode 100644
index 0000000..5e1ceae
--- /dev/null
+++ b/admin/inc/view/plot/Plot.php
@@ -0,0 +1,443 @@
+.
+ *
+ */
+
+ /**
+ * The plot holds graphical attributes, and is responsible for computing the layout of the graph.
+ * The layout is quite simple right now, with 4 areas laid out like that:
+ * (of course this is subject to change in the future).
+ *
+ * output area------------------------------------------------|
+ * | (outer padding) |
+ * | image area--------------------------------------------| |
+ * | | (title padding) | |
+ * | | title area----------------------------------------| | |
+ * | | |-------------------------------------------------| | |
+ * | | | |
+ * | | (graph padding) (caption padding) | |
+ * | | graph area----------------| caption area---------| | |
+ * | | | | | | | |
+ * | | | | | | | |
+ * | | | | | | | |
+ * | | | | | | | |
+ * | | | | | | | |
+ * | | |-------------------------| |--------------------| | |
+ * | | | |
+ * | |-----------------------------------------------------| |
+ * | |
+ * |----------------------------------------------------------|
+ *
+ * All area dimensions are known in advance , and the optional logo is drawn in absolute coordinates.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ * Created on 27 july 2007
+ */
+ class Plot {
+ // Style properties
+ protected $title;
+
+ /**
+ * Location of the logo. Can be overriden to your personalized logo.
+ */
+ protected $logoFileName;
+
+ /**
+ * Outer area, whose dimension is the same as the PNG returned.
+ */
+ protected $outputArea;
+
+ /**
+ * Outer padding surrounding the whole image, everything outside is blank.
+ */
+ protected $outerPadding;
+
+ /**
+ * Coordinates of the area inside the outer padding.
+ */
+ protected $imageArea;
+
+ /**
+ * Fixed title height in pixels.
+ */
+ protected $titleHeight;
+
+ /**
+ * Padding of the title area.
+ */
+ protected $titlePadding;
+
+ /**
+ * Coordinates of the title area.
+ */
+ protected $titleArea;
+
+ /**
+ * True if the plot has a caption.
+ */
+ protected $hasCaption;
+
+ /**
+ * Ratio of graph/caption in width.
+ */
+ protected $graphCaptionRatio;
+
+ /**
+ * Padding of the graph area.
+ */
+ protected $graphPadding;
+
+ /**
+ * Coordinates of the graph area.
+ */
+ protected $graphArea;
+
+ /**
+ * Padding of the caption area.
+ */
+ protected $captionPadding;
+
+ /**
+ * Coordinates of the caption area.
+ */
+ protected $captionArea;
+
+ /**
+ * Text writer.
+ */
+ protected $text;
+
+ /**
+ * Color palette.
+ */
+ protected $palette;
+
+ /**
+ * GD image
+ */
+ protected $img;
+
+ /**
+ * Drawing primitives
+ */
+ protected $primitive;
+
+ protected $backGroundColor;
+ protected $textColor;
+
+ /**
+ * Constructor of Plot.
+ *
+ * @param integer width of the image
+ * @param integer height of the image
+ */
+ public function Plot($width, $height) {
+ $this->width = $width;
+ $this->height = $height;
+
+ $this->text = new Text();
+ $this->palette = new Palette();
+
+ // Default layout
+ $this->outputArea = new Rectangle(0, 0, $width - 1, $height - 1);
+ $this->outerPadding = new Padding(5);
+ $this->titleHeight = 26;
+ $this->titlePadding = new Padding(5);
+ $this->hasCaption = false;
+ $this->graphCaptionRatio = 0.50;
+ $this->graphPadding = new Padding(50);
+ $this->captionPadding = new Padding(15);
+ }
+
+ /**
+ * Compute the area inside the outer padding (outside is white).
+ */
+ private function computeImageArea() {
+ $this->imageArea = $this->outputArea->getPaddedRectangle($this->outerPadding);
+ }
+
+ /**
+ * Compute the title area.
+ */
+ private function computeTitleArea() {
+ $titleUnpaddedBottom = $this->imageArea->y1 + $this->titleHeight + $this->titlePadding->top + $this->titlePadding->bottom;
+ $titleArea = new Rectangle(
+ $this->imageArea->x1,
+ $this->imageArea->y1,
+ $this->imageArea->x2,
+ $titleUnpaddedBottom - 1
+ );
+ $this->titleArea = $titleArea->getPaddedRectangle($this->titlePadding);
+ }
+
+ /**
+ * Compute the graph area.
+ */
+ private function computeGraphArea() {
+ $titleUnpaddedBottom = $this->imageArea->y1 + $this->titleHeight + $this->titlePadding->top + $this->titlePadding->bottom;
+ $graphArea = null;
+ if ($this->hasCaption) {
+ $graphUnpaddedRight = $this->imageArea->x1 + ($this->imageArea->x2 - $this->imageArea->x1) * $this->graphCaptionRatio
+ + $this->graphPadding->left + $this->graphPadding->right;
+ $graphArea = new Rectangle(
+ $this->imageArea->x1,
+ $titleUnpaddedBottom,
+ $graphUnpaddedRight - 1,
+ $this->imageArea->y2
+ );
+ } else {
+ $graphArea = new Rectangle(
+ $this->imageArea->x1,
+ $titleUnpaddedBottom,
+ $this->imageArea->x2,
+ $this->imageArea->y2
+ );
+ }
+ $this->graphArea = $graphArea->getPaddedRectangle($this->graphPadding);
+ }
+
+ /**
+ * Compute the caption area.
+ */
+ private function computeCaptionArea() {
+ $graphUnpaddedRight = $this->imageArea->x1 + ($this->imageArea->x2 - $this->imageArea->x1) * $this->graphCaptionRatio
+ + $this->graphPadding->left + $this->graphPadding->right;
+ $titleUnpaddedBottom = $this->imageArea->y1 + $this->titleHeight + $this->titlePadding->top + $this->titlePadding->bottom;
+ $captionArea = new Rectangle(
+ $graphUnpaddedRight,
+ $titleUnpaddedBottom,
+ $this->imageArea->x2,
+ $this->imageArea->y2
+ );
+ $this->captionArea = $captionArea->getPaddedRectangle($this->captionPadding);
+ }
+
+ /**
+ * Compute the layout of all areas of the graph.
+ */
+ public function computeLayout() {
+ $this->computeImageArea();
+ $this->computeTitleArea();
+ $this->computeGraphArea();
+ if ($this->hasCaption) {
+ $this->computeCaptionArea();
+ }
+ }
+
+ /**
+ * Creates and initialize the image.
+ */
+ public function createImage() {
+ $this->img = imagecreatetruecolor($this->width, $this->height);
+
+ $this->primitive = new Primitive($this->img);
+
+ $this->backGroundColor = new Color(255, 255, 255);
+ $this->textColor = new Color(0, 0, 0);
+
+ // White background
+ imagefilledrectangle($this->img, 0, 0, $this->width - 1, $this->height - 1, $this->backGroundColor->getColor($this->img));
+
+ //imagerectangle($this->img, $this->imageArea->x1, $this->imageArea->y1, $this->imageArea->x2, $this->imageArea->y2, $this->palette->red->getColor($this->img));
+ }
+
+ /**
+ * Print the title to the image.
+ */
+ public function printTitle() {
+ $yCenter = $this->titleArea->y1 + ($this->titleArea->y2 - $this->titleArea->y1) / 2;
+ $this->text->printCentered($this->img, $yCenter, $this->textColor, $this->title, $this->text->fontCondensedBold);
+ }
+
+ /**
+ * Print the logo image to the image.
+ */
+ public function printLogo() {
+ @$logoImage = imageCreateFromPNG($this->logoFileName);
+
+ if ($logoImage) {
+ imagecopymerge($this->img, $logoImage, 2 * $this->outerPadding->left, $this->outerPadding->top, 0, 0, imagesx($logoImage), imagesy($logoImage), 100);
+ }
+ }
+
+ /**
+ * Renders to a file or to standard output.
+ *
+ * @param fileName File name (optional)
+ */
+ public function render($fileName) {
+ if (isset($fileName)) {
+ imagepng($this->img, $fileName);
+ } else {
+ imagepng($this->img);
+ }
+ }
+
+ /**
+ * Sets the title.
+ *
+ * @param string New title
+ */
+ public function setTitle($title) {
+ $this->title = $title;
+ }
+
+ /**
+ * Sets the logo image file name.
+ *
+ * @param string New logo image file name
+ */
+ public function setLogoFileName($logoFileName) {
+ $this->logoFileName = $logoFileName;
+ }
+
+ /**
+ * Return the GD image.
+ *
+ * @return GD Image
+ */
+ public function getImg() {
+ return $this->img;
+ }
+
+ /**
+ * Return the palette.
+ *
+ * @return palette
+ */
+ public function getPalette() {
+ return $this->palette;
+ }
+
+ /**
+ * Return the text.
+ *
+ * @return text
+ */
+ public function getText() {
+ return $this->text;
+ }
+
+ /**
+ * Return the primitive.
+ *
+ * @return primitive
+ */
+ public function getPrimitive() {
+ return $this->primitive;
+ }
+
+ /**
+ * Return the outer padding.
+ *
+ * @param integer Outer padding value in pixels
+ */
+ public function getOuterPadding() {
+ return $outerPadding;
+ }
+
+ /**
+ * Set the outer padding.
+ *
+ * @param integer Outer padding value in pixels
+ */
+ public function setOuterPadding($outerPadding) {
+ $this->outerPadding = $outerPadding;
+ }
+
+ /**
+ * Return the title height.
+ *
+ * @param integer title height
+ */
+ public function setTitleHeight($titleHeight) {
+ $this->titleHeight = $titleHeight;
+ }
+
+ /**
+ * Return the title padding.
+ *
+ * @param integer title padding
+ */
+ public function setTitlePadding($titlePadding) {
+ $this->titlePadding = $titlePadding;
+ }
+
+ /**
+ * Return the graph padding.
+ *
+ * @param integer graph padding
+ */
+ public function setGraphPadding($graphPadding) {
+ $this->graphPadding = $graphPadding;
+ }
+
+ /**
+ * Set if the graph has a caption.
+ *
+ * @param boolean graph has a caption
+ */
+ public function setHasCaption($hasCaption) {
+ $this->hasCaption = $hasCaption;
+ }
+
+ /**
+ * Set the caption padding.
+ *
+ * @param integer caption padding
+ */
+ public function setCaptionPadding($captionPadding) {
+ $this->captionPadding = $captionPadding;
+ }
+
+ /**
+ * Set the graph/caption ratio.
+ *
+ * @param integer caption padding
+ */
+ public function setGraphCaptionRatio($graphCaptionRatio) {
+ $this->graphCaptionRatio = $graphCaptionRatio;
+ }
+
+ /**
+ * Return the graph area.
+ *
+ * @return graph area
+ */
+ public function getGraphArea() {
+ return $this->graphArea;
+ }
+
+ /**
+ * Return the caption area.
+ *
+ * @return caption area
+ */
+ public function getCaptionArea() {
+ return $this->captionArea;
+ }
+
+ /**
+ * Return the text color.
+ *
+ * @return text color
+ */
+ public function getTextColor() {
+ return $this->textColor;
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/primitive/Padding.php b/admin/inc/view/primitive/Padding.php
new file mode 100644
index 0000000..4fd3613
--- /dev/null
+++ b/admin/inc/view/primitive/Padding.php
@@ -0,0 +1,68 @@
+.
+ *
+ */
+
+ /**
+ * Primitive geometric object representing a padding.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ * @Created on 27 july 2007
+ */
+ class Padding {
+ /**
+ * Top padding.
+ */
+ public $top;
+
+ /**
+ * Right padding.
+ */
+ public $right;
+
+ /**
+ * Bottom padding.
+ */
+ public $bottom;
+
+ /**
+ * Left padding.
+ */
+ public $left;
+
+ /**
+ * Creates a new padding.
+ *
+ * @param integer Top padding
+ * @param integer Right padding
+ * @param integer Bottom padding
+ * @param integer Left padding
+ */
+ public function Padding($top, $right = null, $bottom = null, $left = null) {
+ $this->top = $top;
+ if ($right == null) {
+ $this->right = $top;
+ $this->bottom = $top;
+ $this->left = $top;
+ } else {
+ $this->right = $right;
+ $this->bottom = $bottom;
+ $this->left = $left;
+ }
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/primitive/Primitive.php b/admin/inc/view/primitive/Primitive.php
new file mode 100644
index 0000000..c3f8413
--- /dev/null
+++ b/admin/inc/view/primitive/Primitive.php
@@ -0,0 +1,70 @@
+.
+ *
+ */
+
+ /**
+ * Graphic primitives, extends GD with chart related primitives.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ */
+ class Primitive {
+ private $img;
+
+ /**
+ * Creates a new primitive object
+ *
+ * @param resource GD image resource
+ */
+ public function Primitive($img) {
+ $this->img = $img;
+ }
+
+ /**
+ * Draws a straight line.
+ *
+ * @param integer line start (X)
+ * @param integer line start (Y)
+ * @param integer line end (X)
+ * @param integer line end (Y)
+ * @param Color line color
+ */
+ public function line($x1, $y1, $x2, $y2, $color, $width = 1) {
+ imagefilledpolygon($this->img, array($x1, $y1 - $width / 2, $x1, $y1 + $width / 2, $x2, $y2 + $width / 2, $x2, $y2 - $width / 2), 4, $color->getColor($this->img));
+ // imageline($this->img, $x1, $y1, $x2, $y2, $color->getColor($this->img));
+ }
+
+ /**
+ * Draw a filled gray box with thick borders and darker corners.
+ *
+ * @param integer top left coordinate (x)
+ * @param integer top left coordinate (y)
+ * @param integer bottom right coordinate (x)
+ * @param integer bottom right coordinate (y)
+ * @param Color edge color
+ * @param Color corner color
+ */
+ public function outlinedBox($x1, $y1, $x2, $y2, $color0, $color1) {
+ imagefilledrectangle($this->img, $x1, $y1, $x2, $y2, $color0->getColor($this->img));
+ imagerectangle($this->img, $x1, $y1, $x1 + 1, $y1 + 1, $color1->getColor($this->img));
+ imagerectangle($this->img, $x2 - 1, $y1, $x2, $y1 + 1, $color1->getColor($this->img));
+ imagerectangle($this->img, $x1, $y2 - 1, $x1 + 1, $y2, $color1->getColor($this->img));
+ imagerectangle($this->img, $x2 - 1, $y2 - 1, $x2, $y2, $color1->getColor($this->img));
+ }
+
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/primitive/Rectangle.php b/admin/inc/view/primitive/Rectangle.php
new file mode 100644
index 0000000..5defa7b
--- /dev/null
+++ b/admin/inc/view/primitive/Rectangle.php
@@ -0,0 +1,80 @@
+.
+ *
+ */
+
+ /**
+ * A rectangle identified by the top-left and the bottom-right corners.
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ * @Created on 27 july 2007
+ */
+ class Rectangle {
+ /**
+ * Top left X.
+ */
+ public $x1;
+
+ /**
+ * Top left Y.
+ */
+ public $y1;
+
+ /**
+ * Bottom right X.
+ */
+ public $x2;
+
+ /**
+ * Bottom right Y.
+ */
+ public $y2;
+
+ /**
+ * Constructor of Rectangle.
+ *
+ * @param x1 Left edge coordinate
+ * @param y1 Upper edge coordinate
+ * @param x2 Right edge coordinate
+ * @param y2 Bottom edge coordinate
+ */
+ public function Rectangle($x1, $y1, $x2, $y2) {
+ $this->x1 = $x1;
+ $this->y1 = $y1;
+ $this->x2 = $x2;
+ $this->y2 = $y2;
+ }
+
+ /**
+ * Apply a padding and returns the resulting rectangle.
+ * The result is an enlarged rectangle.
+ *
+ * @return Padded rectangle
+ */
+ public function getPaddedRectangle($padding) {
+ $rectangle = new Rectangle(
+ $this->x1 + $padding->left,
+ $this->y1 + $padding->top,
+ $this->x2 - $padding->right,
+ $this->y2 - $padding->bottom
+ );
+
+ //echo "(" . $this->x1 . "," . $this->y1 . ") (" . $this->x2 . "," . $this->y2 . ") ";
+ return $rectangle;
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/inc/view/text/Text.php b/admin/inc/view/text/Text.php
new file mode 100644
index 0000000..6e1db81
--- /dev/null
+++ b/admin/inc/view/text/Text.php
@@ -0,0 +1,129 @@
+.
+ *
+ */
+
+ /**
+ * Text drawing helper
+ *
+ * @author Jean-Marc Trmeaux (jm.tremeaux at gmail.com)
+ */
+ class Text {
+ public $HORIZONTAL_LEFT_ALIGN = 1;
+ public $HORIZONTAL_CENTER_ALIGN = 2;
+ public $HORIZONTAL_RIGHT_ALIGN = 4;
+ public $VERTICAL_TOP_ALIGN = 8;
+ public $VERTICAL_CENTER_ALIGN = 16;
+ public $VERTICAL_BOTTOM_ALIGN = 32;
+
+ /**
+ * Creates a new text drawing helper.
+ */
+ public function Text() {
+ $baseDir = dirname(__FILE__) . "/../../../";
+
+ // Free low-res fonts based on Bitstream Vera
+ $this->fontCondensed = $baseDir . "fonts/DejaVuSansCondensed.ttf";
+ $this->fontCondensedBold = $baseDir . "fonts/DejaVuSansCondensed-Bold.ttf";
+ }
+
+ /**
+ * Print text.
+ *
+ * @param Image GD image
+ * @param integer text coordinate (x)
+ * @param integer text coordinate (y)
+ * @param Color text color
+ * @param string text value
+ * @param string font file name
+ * @param bitfield text alignment
+ */
+ public function printText($img, $px, $py, $color, $text, $fontFileName, $align = 0) {
+ if (!($align & $this->HORIZONTAL_CENTER_ALIGN) && !($align & $this->HORIZONTAL_RIGHT_ALIGN)) {
+ $align |= $this->HORIZONTAL_LEFT_ALIGN;
+ }
+
+ if (!($align & $this->VERTICAL_CENTER_ALIGN) && !($align & $this->VERTICAL_BOTTOM_ALIGN)) {
+ $align |= $this->VERTICAL_TOP_ALIGN;
+ }
+
+ $fontSize = 8;
+ $lineSpacing = 1;
+
+ list ($llx, $lly, $lrx, $lry, $urx, $ury, $ulx, $uly) = imageftbbox($fontSize, 0, $fontFileName, $text, array("linespacing" => $lineSpacing));
+
+ $textWidth = $lrx - $llx;
+ $textHeight = $lry - $ury;
+
+ $angle = 0;
+
+ if ($align & $this->HORIZONTAL_CENTER_ALIGN) {
+ $px -= $textWidth / 2;
+ }
+
+ if ($align & $this->HORIZONTAL_RIGHT_ALIGN) {
+ $px -= $textWidth;
+ }
+
+ if ($align & $this->VERTICAL_CENTER_ALIGN) {
+ $py += $textHeight / 2;
+ }
+
+ if ($align & $this->VERTICAL_TOP_ALIGN) {
+ $py += $textHeight;
+ }
+
+ imagettftext($img, $fontSize, $angle, $px, $py, $color->getColor($img), $fontFileName, $text);
+ }
+
+ /**
+ * Print text centered horizontally on the image.
+ *
+ * @param Image GD image
+ * @param integer text coordinate (y)
+ * @param Color text color
+ * @param string text value
+ * @param string font file name
+ */
+ public function printCentered($img, $py, $color, $text, $fontFileName) {
+ $this->printText($img, imagesx($img) / 2, $py, $color, $text, $fontFileName, $this->HORIZONTAL_CENTER_ALIGN | $this->VERTICAL_CENTER_ALIGN);
+ }
+
+ /**
+ * Print text in diagonal.
+ *
+ * @param Image GD image
+ * @param integer text coordinate (x)
+ * @param integer text coordinate (y)
+ * @param Color text color
+ * @param string text value
+ */
+ public function printDiagonal($img, $px, $py, $color, $text) {
+ $fontSize = 8;
+ $fontFileName = $this->fontCondensed;
+
+ $lineSpacing = 1;
+
+ list ($lx, $ly, $rx, $ry) = imageftbbox($fontSize, 0, $fontFileName, $text, array("linespacing" => $lineSpacing));
+ $textWidth = $rx - $lx;
+
+ $angle = -45;
+
+ imagettftext($img, $fontSize, $angle, $px, $py, $color->getColor($img), $fontFileName, $text);
+ }
+ }
+?>
\ No newline at end of file
diff --git a/admin/index.php b/admin/index.php
new file mode 100644
index 0000000..fce5a8b
--- /dev/null
+++ b/admin/index.php
@@ -0,0 +1,150 @@
+
+
+
+
+
+
+
+
+
+
+
+
Vous êtes authentifié sous l'utilisateur :
+
+ Se déconnecter
+
+
+
Informations générales
+
+ '.$nbs.' mails viennent d\'être envoyés en '.$plt.' sec
La première chose à faire est l'importation de la liste des coopérateurs dans la table "clients".
+
Il faut ensuite générer les mots de passe via le bouton du menu.
+
Une fois les mots de passe générés, on peut passer à l'activation du système de vote.
+
Enfin, il reste à envoyer les mails de convocation, toujours dans le menu.
+
+
+
État de la plateforme de vote
+ prepare($sql);
+
+ // On regarde si le vote est ouvert
+ $option = 'vote_ouvert';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ // Exécution de la requête
+ $stmt->execute();
+ $results = $stmt->fetch(); ?>
+
+
+ État du vote :
+
+ prepare($sql);
+ // On regarde si le vote est ouvert
+ $option = 'vote_ouvert';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ // Exécution de la requête
+ $stmt->execute();
+ $results = $stmt->fetch();
+
+ if($results['option_value'] == 0)
+ {
+ echo "checked";
+ }
+ ?>>
+
+
+
+
+ bindParam(':option', $option, PDO::PARAM_STR);
+ // Exécution de la requête
+ $stmt->execute();
+ $results = $stmt->fetch();
+ echo "
État des mots de passe : ";
+ if ($results['option_value'] == 1) {echo "Générés
";}
+ else { echo "Pas générés";};
+
+ // On regarde le nombre de mails envoyés
+ $option = 'nb_mail_sent';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ // Exécution de la requête
+ $stmt->execute();
+ $results = $stmt->fetch();
+ $nb_mail_sent = $results['option_value'];
+ $_SESSION['nb_mail_sent'] = $nb_mail_sent;
+ echo "
";
+ if ($nb_mail_sent !== "0") {
+ echo "Nombre de mails envoyés : ".$nb_mail_sent."
";
+ // On regade quand le dernier mail est parti
+ $option = 'last_mail_sent';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ // Exécution de la requête
+ $stmt->execute();
+ $results = $stmt->fetch();
+ $last_mail_sent = $results['option_value'];
+ $_SESSION['last_mail_sent'] = $last_mail_sent;
+ echo "
Dernier mail envoyé le : ";
+ if ($last_mail_sent !== NULL) {echo $last_mail_sent."
";} else { echo "Pas de date ?!";};
+ } else { echo "La convocation n'est pas envoyée.";};
+
+ //Clore la requête
+ $stmt->closeCursor();
+ $stmt = null;
+ ?>
+
+
Bon à savoir
+
La génération des mots de passe est effectuée uniquement pour les coopérateurs sans mot de passe. Un mot de passe existant ne sera jamais remplacé.
+
Les mails sont envoyés pas paquets de 75. Vous serez averti dans la zone de notification s'il reste des coopérateurs n'ayant pas reçu le mail, ou s'il n'y a plus de mail à envoyer.
+
L'envoi des mails ignore systématiquement les coopérateurs ayant déjà voté.
+
L'envoi des mails utilise l'adresse ag@ouvaton.coop, créée sur le compte gl-ouv. La limite de mails/heure de cette adresse est de 4000.
+
+
+
+
+
+
+
+
+
diff --git a/admin/js/Chart.js b/admin/js/Chart.js
new file mode 100644
index 0000000..2c2b608
--- /dev/null
+++ b/admin/js/Chart.js
@@ -0,0 +1,3379 @@
+/*!
+ * Chart.js
+ * http://chartjs.org/
+ * Version: 1.0.1-beta.4
+ *
+ * Copyright 2014 Nick Downie
+ * Released under the MIT license
+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
+ */
+
+
+(function(){
+
+ "use strict";
+
+ //Declare root variable - window in the browser, global on the server
+ var root = this,
+ previous = root.Chart;
+
+ //Occupy the global variable of Chart, and create a simple base class
+ var Chart = function(context){
+ var chart = this;
+ this.canvas = context.canvas;
+
+ this.ctx = context;
+
+ //Variables global to the chart
+ var width = this.width = context.canvas.width;
+ var height = this.height = context.canvas.height;
+ this.aspectRatio = this.width / this.height;
+ //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
+ helpers.retinaScale(this);
+
+ return this;
+ };
+ //Globally expose the defaults to allow for user updating/changing
+ Chart.defaults = {
+ global: {
+ // Boolean - Whether to animate the chart
+ animation: true,
+
+ // Number - Number of animation steps
+ animationSteps: 60,
+
+ // String - Animation easing effect
+ animationEasing: "easeOutQuart",
+
+ // Boolean - If we should show the scale at all
+ showScale: true,
+
+ // Boolean - If we want to override with a hard coded scale
+ scaleOverride: false,
+
+ // ** Required if scaleOverride is true **
+ // Number - The number of steps in a hard coded scale
+ scaleSteps: null,
+ // Number - The value jump in the hard coded scale
+ scaleStepWidth: null,
+ // Number - The scale starting value
+ scaleStartValue: null,
+
+ // String - Colour of the scale line
+ scaleLineColor: "rgba(0,0,0,.1)",
+
+ // Number - Pixel width of the scale line
+ scaleLineWidth: 1,
+
+ // Boolean - Whether to show labels on the scale
+ scaleShowLabels: true,
+
+ // Interpolated JS string - can access value
+ scaleLabel: "<%=value%>",
+
+ // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
+ scaleIntegersOnly: true,
+
+ // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
+ scaleBeginAtZero: false,
+
+ // String - Scale label font declaration for the scale label
+ scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+ // Number - Scale label font size in pixels
+ scaleFontSize: 12,
+
+ // String - Scale label font weight style
+ scaleFontStyle: "normal",
+
+ // String - Scale label font colour
+ scaleFontColor: "#666",
+
+ // Boolean - whether or not the chart should be responsive and resize when the browser does.
+ responsive: false,
+
+ // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
+ maintainAspectRatio: true,
+
+ // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
+ showTooltips: true,
+
+ // Array - Array of string names to attach tooltip events
+ tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
+
+ // String - Tooltip background colour
+ tooltipFillColor: "rgba(0,0,0,0.8)",
+
+ // String - Tooltip label font declaration for the scale label
+ tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+ // Number - Tooltip label font size in pixels
+ tooltipFontSize: 14,
+
+ // String - Tooltip font weight style
+ tooltipFontStyle: "normal",
+
+ // String - Tooltip label font colour
+ tooltipFontColor: "#fff",
+
+ // String - Tooltip title font declaration for the scale label
+ tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+ // Number - Tooltip title font size in pixels
+ tooltipTitleFontSize: 14,
+
+ // String - Tooltip title font weight style
+ tooltipTitleFontStyle: "bold",
+
+ // String - Tooltip title font colour
+ tooltipTitleFontColor: "#fff",
+
+ // Number - pixel width of padding around tooltip text
+ tooltipYPadding: 6,
+
+ // Number - pixel width of padding around tooltip text
+ tooltipXPadding: 6,
+
+ // Number - Size of the caret on the tooltip
+ tooltipCaretSize: 8,
+
+ // Number - Pixel radius of the tooltip border
+ tooltipCornerRadius: 6,
+
+ // Number - Pixel offset from point x to tooltip edge
+ tooltipXOffset: 10,
+
+ // String - Template string for single tooltips
+ tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
+
+ // String - Template string for single tooltips
+ multiTooltipTemplate: "<%= value %>",
+
+ // String - Colour behind the legend colour block
+ multiTooltipKeyBackground: '#fff',
+
+ // Function - Will fire on animation progression.
+ onAnimationProgress: function(){},
+
+ // Function - Will fire on animation completion.
+ onAnimationComplete: function(){}
+
+ }
+ };
+
+ //Create a dictionary of chart types, to allow for extension of existing types
+ Chart.types = {};
+
+ //Global Chart helpers object for utility methods and classes
+ var helpers = Chart.helpers = {};
+
+ //-- Basic js utility methods
+ var each = helpers.each = function(loopable,callback,self){
+ var additionalArgs = Array.prototype.slice.call(arguments, 3);
+ // Check to see if null or undefined firstly.
+ if (loopable){
+ if (loopable.length === +loopable.length){
+ var i;
+ for (i=0; i= 0; i--) {
+ var currentItem = arrayToSearch[i];
+ if (filterCallback(currentItem)){
+ return currentItem;
+ }
+ };
+ },
+ inherits = helpers.inherits = function(extensions){
+ //Basic javascript inheritance based on the model created in Backbone.js
+ var parent = this;
+ var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };
+
+ var Surrogate = function(){ this.constructor = ChartElement;};
+ Surrogate.prototype = parent.prototype;
+ ChartElement.prototype = new Surrogate();
+
+ ChartElement.extend = inherits;
+
+ if (extensions) extend(ChartElement.prototype, extensions);
+
+ ChartElement.__super__ = parent.prototype;
+
+ return ChartElement;
+ },
+ noop = helpers.noop = function(){},
+ uid = helpers.uid = (function(){
+ var id=0;
+ return function(){
+ return "chart-" + id++;
+ };
+ })(),
+ warn = helpers.warn = function(str){
+ //Method for warning of errors
+ if (window.console && typeof window.console.warn == "function") console.warn(str);
+ },
+ amd = helpers.amd = (typeof define == 'function' && define.amd),
+ //-- Math methods
+ isNumber = helpers.isNumber = function(n){
+ return !isNaN(parseFloat(n)) && isFinite(n);
+ },
+ max = helpers.max = function(array){
+ return Math.max.apply( Math, array );
+ },
+ min = helpers.min = function(array){
+ return Math.min.apply( Math, array );
+ },
+ cap = helpers.cap = function(valueToCap,maxValue,minValue){
+ if(isNumber(maxValue)) {
+ if( valueToCap > maxValue ) {
+ return maxValue;
+ }
+ }
+ else if(isNumber(minValue)){
+ if ( valueToCap < minValue ){
+ return minValue;
+ }
+ }
+ return valueToCap;
+ },
+ getDecimalPlaces = helpers.getDecimalPlaces = function(num){
+ if (num%1!==0 && isNumber(num)){
+ return num.toString().split(".")[1].length;
+ }
+ else {
+ return 0;
+ }
+ },
+ toRadians = helpers.radians = function(degrees){
+ return degrees * (Math.PI/180);
+ },
+ // Gets the angle from vertical upright to the point about a centre.
+ getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
+ var distanceFromXCenter = anglePoint.x - centrePoint.x,
+ distanceFromYCenter = anglePoint.y - centrePoint.y,
+ radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
+
+
+ var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
+
+ //If the segment is in the top left quadrant, we need to add another rotation to the angle
+ if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
+ angle += Math.PI*2;
+ }
+
+ return {
+ angle: angle,
+ distance: radialDistanceFromCenter
+ };
+ },
+ aliasPixel = helpers.aliasPixel = function(pixelWidth){
+ return (pixelWidth % 2 === 0) ? 0 : 0.5;
+ },
+ splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
+ //Props to Rob Spencer at scaled innovation for his post on splining between points
+ //http://scaledinnovation.com/analytics/splines/aboutSplines.html
+ var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
+ d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
+ fa=t*d01/(d01+d12),// scaling factor for triangle Ta
+ fb=t*d12/(d01+d12);
+ return {
+ inner : {
+ x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
+ y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
+ },
+ outer : {
+ x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
+ y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
+ }
+ };
+ },
+ calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
+ return Math.floor(Math.log(val) / Math.LN10);
+ },
+ calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){
+
+ //Set a minimum step of two - a point at the top of the graph, and a point at the base
+ var minSteps = 2,
+ maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
+ skipFitting = (minSteps >= maxSteps);
+
+ var maxValue = max(valuesArray),
+ minValue = min(valuesArray);
+
+ // We need some degree of seperation here to calculate the scales if all the values are the same
+ // Adding/minusing 0.5 will give us a range of 1.
+ if (maxValue === minValue){
+ maxValue += 0.5;
+ // So we don't end up with a graph with a negative start value if we've said always start from zero
+ if (minValue >= 0.5 && !startFromZero){
+ minValue -= 0.5;
+ }
+ else{
+ // Make up a whole number above the values
+ maxValue += 0.5;
+ }
+ }
+
+ var valueRange = Math.abs(maxValue - minValue),
+ rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
+ graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
+ graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
+ graphRange = graphMax - graphMin,
+ stepValue = Math.pow(10, rangeOrderOfMagnitude),
+ numberOfSteps = Math.round(graphRange / stepValue);
+
+ //If we have more space on the graph we'll use it to give more definition to the data
+ while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
+ if(numberOfSteps > maxSteps){
+ stepValue *=2;
+ numberOfSteps = Math.round(graphRange/stepValue);
+ // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
+ if (numberOfSteps % 1 !== 0){
+ skipFitting = true;
+ }
+ }
+ //We can fit in double the amount of scale points on the scale
+ else{
+ //If user has declared ints only, and the step value isn't a decimal
+ if (integersOnly && rangeOrderOfMagnitude >= 0){
+ //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
+ if(stepValue/2 % 1 === 0){
+ stepValue /=2;
+ numberOfSteps = Math.round(graphRange/stepValue);
+ }
+ //If it would make it a float break out of the loop
+ else{
+ break;
+ }
+ }
+ //If the scale doesn't have to be an int, make the scale more granular anyway.
+ else{
+ stepValue /=2;
+ numberOfSteps = Math.round(graphRange/stepValue);
+ }
+
+ }
+ }
+
+ if (skipFitting){
+ numberOfSteps = minSteps;
+ stepValue = graphRange / numberOfSteps;
+ }
+
+ return {
+ steps : numberOfSteps,
+ stepValue : stepValue,
+ min : graphMin,
+ max : graphMin + (numberOfSteps * stepValue)
+ };
+
+ },
+ /* jshint ignore:start */
+ // Blows up jshint errors based on the new Function constructor
+ //Templating methods
+ //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
+ template = helpers.template = function(templateString, valuesObject){
+ // If templateString is function rather than string-template - call the function for valuesObject
+ if(templateString instanceof Function){
+ return templateString(valuesObject);
+ }
+
+ var cache = {};
+ function tmpl(str, data){
+ // Figure out if we're getting a template, or if we need to
+ // load the template - and be sure to cache the result.
+ var fn = !/\W/.test(str) ?
+ cache[str] = cache[str] :
+
+ // Generate a reusable function that will serve as a template
+ // generator (and which will be cached).
+ new Function("obj",
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
+
+ // Introduce the data as local variables using with(){}
+ "with(obj){p.push('" +
+
+ // Convert the template into pure JavaScript
+ str
+ .replace(/[\r\t\n]/g, " ")
+ .split("<%").join("\t")
+ .replace(/((^|%>)[^\t]*)'/g, "$1\r")
+ .replace(/\t=(.*?)%>/g, "',$1,'")
+ .split("\t").join("');")
+ .split("%>").join("p.push('")
+ .split("\r").join("\\'") +
+ "');}return p.join('');"
+ );
+
+ // Provide some basic currying to the user
+ return data ? fn( data ) : fn;
+ }
+ return tmpl(templateString,valuesObject);
+ },
+ /* jshint ignore:end */
+ generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
+ var labelsArray = new Array(numberOfSteps);
+ if (labelTemplateString){
+ each(labelsArray,function(val,index){
+ labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
+ });
+ }
+ return labelsArray;
+ },
+ //--Animation methods
+ //Easing functions adapted from Robert Penner's easing equations
+ //http://www.robertpenner.com/easing/
+ easingEffects = helpers.easingEffects = {
+ linear: function (t) {
+ return t;
+ },
+ easeInQuad: function (t) {
+ return t * t;
+ },
+ easeOutQuad: function (t) {
+ return -1 * t * (t - 2);
+ },
+ easeInOutQuad: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
+ return -1 / 2 * ((--t) * (t - 2) - 1);
+ },
+ easeInCubic: function (t) {
+ return t * t * t;
+ },
+ easeOutCubic: function (t) {
+ return 1 * ((t = t / 1 - 1) * t * t + 1);
+ },
+ easeInOutCubic: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
+ return 1 / 2 * ((t -= 2) * t * t + 2);
+ },
+ easeInQuart: function (t) {
+ return t * t * t * t;
+ },
+ easeOutQuart: function (t) {
+ return -1 * ((t = t / 1 - 1) * t * t * t - 1);
+ },
+ easeInOutQuart: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
+ return -1 / 2 * ((t -= 2) * t * t * t - 2);
+ },
+ easeInQuint: function (t) {
+ return 1 * (t /= 1) * t * t * t * t;
+ },
+ easeOutQuint: function (t) {
+ return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
+ },
+ easeInOutQuint: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
+ return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
+ },
+ easeInSine: function (t) {
+ return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
+ },
+ easeOutSine: function (t) {
+ return 1 * Math.sin(t / 1 * (Math.PI / 2));
+ },
+ easeInOutSine: function (t) {
+ return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
+ },
+ easeInExpo: function (t) {
+ return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
+ },
+ easeOutExpo: function (t) {
+ return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
+ },
+ easeInOutExpo: function (t) {
+ if (t === 0) return 0;
+ if (t === 1) return 1;
+ if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
+ return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
+ },
+ easeInCirc: function (t) {
+ if (t >= 1) return t;
+ return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
+ },
+ easeOutCirc: function (t) {
+ return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
+ },
+ easeInOutCirc: function (t) {
+ if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
+ return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
+ },
+ easeInElastic: function (t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) return 0;
+ if ((t /= 1) == 1) return 1;
+ if (!p) p = 1 * 0.3;
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+ },
+ easeOutElastic: function (t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) return 0;
+ if ((t /= 1) == 1) return 1;
+ if (!p) p = 1 * 0.3;
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
+ },
+ easeInOutElastic: function (t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) return 0;
+ if ((t /= 1 / 2) == 2) return 1;
+ if (!p) p = 1 * (0.3 * 1.5);
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+ return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
+ },
+ easeInBack: function (t) {
+ var s = 1.70158;
+ return 1 * (t /= 1) * t * ((s + 1) * t - s);
+ },
+ easeOutBack: function (t) {
+ var s = 1.70158;
+ return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
+ },
+ easeInOutBack: function (t) {
+ var s = 1.70158;
+ if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
+ return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
+ },
+ easeInBounce: function (t) {
+ return 1 - easingEffects.easeOutBounce(1 - t);
+ },
+ easeOutBounce: function (t) {
+ if ((t /= 1) < (1 / 2.75)) {
+ return 1 * (7.5625 * t * t);
+ } else if (t < (2 / 2.75)) {
+ return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
+ } else if (t < (2.5 / 2.75)) {
+ return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
+ } else {
+ return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
+ }
+ },
+ easeInOutBounce: function (t) {
+ if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
+ return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
+ }
+ },
+ //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
+ requestAnimFrame = helpers.requestAnimFrame = (function(){
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(callback) {
+ return window.setTimeout(callback, 1000 / 60);
+ };
+ })(),
+ cancelAnimFrame = helpers.cancelAnimFrame = (function(){
+ return window.cancelAnimationFrame ||
+ window.webkitCancelAnimationFrame ||
+ window.mozCancelAnimationFrame ||
+ window.oCancelAnimationFrame ||
+ window.msCancelAnimationFrame ||
+ function(callback) {
+ return window.clearTimeout(callback, 1000 / 60);
+ };
+ })(),
+ animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
+
+ var currentStep = 0,
+ easingFunction = easingEffects[easingString] || easingEffects.linear;
+
+ var animationFrame = function(){
+ currentStep++;
+ var stepDecimal = currentStep/totalSteps;
+ var easeDecimal = easingFunction(stepDecimal);
+
+ callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
+ onProgress.call(chartInstance,easeDecimal,stepDecimal);
+ if (currentStep < totalSteps){
+ chartInstance.animationFrame = requestAnimFrame(animationFrame);
+ } else{
+ onComplete.apply(chartInstance);
+ }
+ };
+ requestAnimFrame(animationFrame);
+ },
+ //-- DOM methods
+ getRelativePosition = helpers.getRelativePosition = function(evt){
+ var mouseX, mouseY;
+ var e = evt.originalEvent || evt,
+ canvas = evt.currentTarget || evt.srcElement,
+ boundingRect = canvas.getBoundingClientRect();
+
+ if (e.touches){
+ mouseX = e.touches[0].clientX - boundingRect.left;
+ mouseY = e.touches[0].clientY - boundingRect.top;
+
+ }
+ else{
+ mouseX = e.clientX - boundingRect.left;
+ mouseY = e.clientY - boundingRect.top;
+ }
+
+ return {
+ x : mouseX,
+ y : mouseY
+ };
+
+ },
+ addEvent = helpers.addEvent = function(node,eventType,method){
+ if (node.addEventListener){
+ node.addEventListener(eventType,method);
+ } else if (node.attachEvent){
+ node.attachEvent("on"+eventType, method);
+ } else {
+ node["on"+eventType] = method;
+ }
+ },
+ removeEvent = helpers.removeEvent = function(node, eventType, handler){
+ if (node.removeEventListener){
+ node.removeEventListener(eventType, handler, false);
+ } else if (node.detachEvent){
+ node.detachEvent("on"+eventType,handler);
+ } else{
+ node["on" + eventType] = noop;
+ }
+ },
+ bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
+ // Create the events object if it's not already present
+ if (!chartInstance.events) chartInstance.events = {};
+
+ each(arrayOfEvents,function(eventName){
+ chartInstance.events[eventName] = function(){
+ handler.apply(chartInstance, arguments);
+ };
+ addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
+ });
+ },
+ unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
+ each(arrayOfEvents, function(handler,eventName){
+ removeEvent(chartInstance.chart.canvas, eventName, handler);
+ });
+ },
+ getMaximumWidth = helpers.getMaximumWidth = function(domNode){
+ var container = domNode.parentNode;
+ // TODO = check cross browser stuff with this.
+ return container.clientWidth;
+ },
+ getMaximumHeight = helpers.getMaximumHeight = function(domNode){
+ var container = domNode.parentNode;
+ // TODO = check cross browser stuff with this.
+ return container.clientHeight;
+ },
+ getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
+ retinaScale = helpers.retinaScale = function(chart){
+ var ctx = chart.ctx,
+ width = chart.canvas.width,
+ height = chart.canvas.height;
+
+ if (window.devicePixelRatio) {
+ ctx.canvas.style.width = width + "px";
+ ctx.canvas.style.height = height + "px";
+ ctx.canvas.height = height * window.devicePixelRatio;
+ ctx.canvas.width = width * window.devicePixelRatio;
+ ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
+ }
+ },
+ //-- Canvas methods
+ clear = helpers.clear = function(chart){
+ chart.ctx.clearRect(0,0,chart.width,chart.height);
+ },
+ fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
+ return fontStyle + " " + pixelSize+"px " + fontFamily;
+ },
+ longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
+ ctx.font = font;
+ var longest = 0;
+ each(arrayOfStrings,function(string){
+ var textWidth = ctx.measureText(string).width;
+ longest = (textWidth > longest) ? textWidth : longest;
+ });
+ return longest;
+ },
+ drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
+ ctx.beginPath();
+ ctx.moveTo(x + radius, y);
+ ctx.lineTo(x + width - radius, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+ ctx.lineTo(x + width, y + height - radius);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+ ctx.lineTo(x + radius, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+ ctx.lineTo(x, y + radius);
+ ctx.quadraticCurveTo(x, y, x + radius, y);
+ ctx.closePath();
+ };
+
+
+ //Store a reference to each instance - allowing us to globally resize chart instances on window resize.
+ //Destroy method on the chart will remove the instance of the chart from this reference.
+ Chart.instances = {};
+
+ Chart.Type = function(data,options,chart){
+ this.options = options;
+ this.chart = chart;
+ this.id = uid();
+ //Add the chart instance to the global namespace
+ Chart.instances[this.id] = this;
+
+ // Initialize is always called when a chart type is created
+ // By default it is a no op, but it should be extended
+ if (options.responsive){
+ this.resize();
+ }
+ this.initialize.call(this,data);
+ };
+
+ //Core methods that'll be a part of every chart type
+ extend(Chart.Type.prototype,{
+ initialize : function(){return this;},
+ clear : function(){
+ clear(this.chart);
+ return this;
+ },
+ stop : function(){
+ // Stops any current animation loop occuring
+ helpers.cancelAnimFrame.call(root, this.animationFrame);
+ return this;
+ },
+ resize : function(callback){
+ this.stop();
+ var canvas = this.chart.canvas,
+ newWidth = getMaximumWidth(this.chart.canvas),
+ newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
+
+ canvas.width = this.chart.width = newWidth;
+ canvas.height = this.chart.height = newHeight;
+
+ retinaScale(this.chart);
+
+ if (typeof callback === "function"){
+ callback.apply(this, Array.prototype.slice.call(arguments, 1));
+ }
+ return this;
+ },
+ reflow : noop,
+ render : function(reflow){
+ if (reflow){
+ this.reflow();
+ }
+ if (this.options.animation && !reflow){
+ helpers.animationLoop(
+ this.draw,
+ this.options.animationSteps,
+ this.options.animationEasing,
+ this.options.onAnimationProgress,
+ this.options.onAnimationComplete,
+ this
+ );
+ }
+ else{
+ this.draw();
+ this.options.onAnimationComplete.call(this);
+ }
+ return this;
+ },
+ generateLegend : function(){
+ return template(this.options.legendTemplate,this);
+ },
+ destroy : function(){
+ this.clear();
+ unbindEvents(this, this.events);
+ delete Chart.instances[this.id];
+ },
+ showTooltip : function(ChartElements, forceRedraw){
+ // Only redraw the chart if we've actually changed what we're hovering on.
+ if (typeof this.activeElements === 'undefined') this.activeElements = [];
+
+ var isChanged = (function(Elements){
+ var changed = false;
+
+ if (Elements.length !== this.activeElements.length){
+ changed = true;
+ return changed;
+ }
+
+ each(Elements, function(element, index){
+ if (element !== this.activeElements[index]){
+ changed = true;
+ }
+ }, this);
+ return changed;
+ }).call(this, ChartElements);
+
+ if (!isChanged && !forceRedraw){
+ return;
+ }
+ else{
+ this.activeElements = ChartElements;
+ }
+ this.draw();
+ if (ChartElements.length > 0){
+ // If we have multiple datasets, show a MultiTooltip for all of the data points at that index
+ if (this.datasets && this.datasets.length > 1) {
+ var dataArray,
+ dataIndex;
+
+ for (var i = this.datasets.length - 1; i >= 0; i--) {
+ dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
+ dataIndex = indexOf(dataArray, ChartElements[0]);
+ if (dataIndex !== -1){
+ break;
+ }
+ }
+ var tooltipLabels = [],
+ tooltipColors = [],
+ medianPosition = (function(index) {
+
+ // Get all the points at that particular index
+ var Elements = [],
+ dataCollection,
+ xPositions = [],
+ yPositions = [],
+ xMax,
+ yMax,
+ xMin,
+ yMin;
+ helpers.each(this.datasets, function(dataset){
+ dataCollection = dataset.points || dataset.bars || dataset.segments;
+ if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
+ Elements.push(dataCollection[dataIndex]);
+ }
+ });
+
+ helpers.each(Elements, function(element) {
+ xPositions.push(element.x);
+ yPositions.push(element.y);
+
+
+ //Include any colour information about the element
+ tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
+ tooltipColors.push({
+ fill: element._saved.fillColor || element.fillColor,
+ stroke: element._saved.strokeColor || element.strokeColor
+ });
+
+ }, this);
+
+ yMin = min(yPositions);
+ yMax = max(yPositions);
+
+ xMin = min(xPositions);
+ xMax = max(xPositions);
+
+ return {
+ x: (xMin > this.chart.width/2) ? xMin : xMax,
+ y: (yMin + yMax)/2
+ };
+ }).call(this, dataIndex);
+
+ new Chart.MultiTooltip({
+ x: medianPosition.x,
+ y: medianPosition.y,
+ xPadding: this.options.tooltipXPadding,
+ yPadding: this.options.tooltipYPadding,
+ xOffset: this.options.tooltipXOffset,
+ fillColor: this.options.tooltipFillColor,
+ textColor: this.options.tooltipFontColor,
+ fontFamily: this.options.tooltipFontFamily,
+ fontStyle: this.options.tooltipFontStyle,
+ fontSize: this.options.tooltipFontSize,
+ titleTextColor: this.options.tooltipTitleFontColor,
+ titleFontFamily: this.options.tooltipTitleFontFamily,
+ titleFontStyle: this.options.tooltipTitleFontStyle,
+ titleFontSize: this.options.tooltipTitleFontSize,
+ cornerRadius: this.options.tooltipCornerRadius,
+ labels: tooltipLabels,
+ legendColors: tooltipColors,
+ legendColorBackground : this.options.multiTooltipKeyBackground,
+ title: ChartElements[0].label,
+ chart: this.chart,
+ ctx: this.chart.ctx
+ }).draw();
+
+ } else {
+ each(ChartElements, function(Element) {
+ var tooltipPosition = Element.tooltipPosition();
+ new Chart.Tooltip({
+ x: Math.round(tooltipPosition.x),
+ y: Math.round(tooltipPosition.y),
+ xPadding: this.options.tooltipXPadding,
+ yPadding: this.options.tooltipYPadding,
+ fillColor: this.options.tooltipFillColor,
+ textColor: this.options.tooltipFontColor,
+ fontFamily: this.options.tooltipFontFamily,
+ fontStyle: this.options.tooltipFontStyle,
+ fontSize: this.options.tooltipFontSize,
+ caretHeight: this.options.tooltipCaretSize,
+ cornerRadius: this.options.tooltipCornerRadius,
+ text: template(this.options.tooltipTemplate, Element),
+ chart: this.chart
+ }).draw();
+ }, this);
+ }
+ }
+ return this;
+ },
+ toBase64Image : function(){
+ return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
+ }
+ });
+
+ Chart.Type.extend = function(extensions){
+
+ var parent = this;
+
+ var ChartType = function(){
+ return parent.apply(this,arguments);
+ };
+
+ //Copy the prototype object of the this class
+ ChartType.prototype = clone(parent.prototype);
+ //Now overwrite some of the properties in the base class with the new extensions
+ extend(ChartType.prototype, extensions);
+
+ ChartType.extend = Chart.Type.extend;
+
+ if (extensions.name || parent.prototype.name){
+
+ var chartName = extensions.name || parent.prototype.name;
+ //Assign any potential default values of the new chart type
+
+ //If none are defined, we'll use a clone of the chart type this is being extended from.
+ //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
+ //doesn't define some defaults of their own.
+
+ var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
+
+ Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);
+
+ Chart.types[chartName] = ChartType;
+
+ //Register this new chart type in the Chart prototype
+ Chart.prototype[chartName] = function(data,options){
+ var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
+ return new ChartType(data,config,this);
+ };
+ } else{
+ warn("Name not provided for this chart, so it hasn't been registered");
+ }
+ return parent;
+ };
+
+ Chart.Element = function(configuration){
+ extend(this,configuration);
+ this.initialize.apply(this,arguments);
+ this.save();
+ };
+ extend(Chart.Element.prototype,{
+ initialize : function(){},
+ restore : function(props){
+ if (!props){
+ extend(this,this._saved);
+ } else {
+ each(props,function(key){
+ this[key] = this._saved[key];
+ },this);
+ }
+ return this;
+ },
+ save : function(){
+ this._saved = clone(this);
+ delete this._saved._saved;
+ return this;
+ },
+ update : function(newProps){
+ each(newProps,function(value,key){
+ this._saved[key] = this[key];
+ this[key] = value;
+ },this);
+ return this;
+ },
+ transition : function(props,ease){
+ each(props,function(value,key){
+ this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
+ },this);
+ return this;
+ },
+ tooltipPosition : function(){
+ return {
+ x : this.x,
+ y : this.y
+ };
+ },
+ hasValue: function(){
+ return isNumber(this.value);
+ }
+ });
+
+ Chart.Element.extend = inherits;
+
+
+ Chart.Point = Chart.Element.extend({
+ display: true,
+ inRange: function(chartX,chartY){
+ var hitDetectionRange = this.hitDetectionRadius + this.radius;
+ return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
+ },
+ draw : function(){
+ if (this.display){
+ var ctx = this.ctx;
+ ctx.beginPath();
+
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
+ ctx.closePath();
+
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ ctx.fillStyle = this.fillColor;
+
+ ctx.fill();
+ ctx.stroke();
+ }
+
+
+ //Quick debug for bezier curve splining
+ //Highlights control points and the line between them.
+ //Handy for dev - stripped in the min version.
+
+ // ctx.save();
+ // ctx.fillStyle = "black";
+ // ctx.strokeStyle = "black"
+ // ctx.beginPath();
+ // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
+ // ctx.fill();
+
+ // ctx.beginPath();
+ // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
+ // ctx.fill();
+
+ // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
+ // ctx.lineTo(this.x, this.y);
+ // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
+ // ctx.stroke();
+
+ // ctx.restore();
+
+
+
+ }
+ });
+
+ Chart.Arc = Chart.Element.extend({
+ inRange : function(chartX,chartY){
+
+ var pointRelativePosition = helpers.getAngleFromPoint(this, {
+ x: chartX,
+ y: chartY
+ });
+
+ //Check if within the range of the open/close angle
+ var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
+ withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
+
+ return (betweenAngles && withinRadius);
+ //Ensure within the outside of the arc centre, but inside arc outer
+ },
+ tooltipPosition : function(){
+ var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
+ rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
+ return {
+ x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
+ y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
+ };
+ },
+ draw : function(animationPercent){
+
+ var easingDecimal = animationPercent || 1;
+
+ var ctx = this.ctx;
+
+ ctx.beginPath();
+
+ ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
+
+ ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
+
+ ctx.closePath();
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ ctx.fillStyle = this.fillColor;
+
+ ctx.fill();
+ ctx.lineJoin = 'bevel';
+
+ if (this.showStroke){
+ ctx.stroke();
+ }
+ }
+ });
+
+ Chart.Rectangle = Chart.Element.extend({
+ draw : function(){
+ var ctx = this.ctx,
+ halfWidth = this.width/2,
+ leftX = this.x - halfWidth,
+ rightX = this.x + halfWidth,
+ top = this.base - (this.base - this.y),
+ halfStroke = this.strokeWidth / 2;
+
+ // Canvas doesn't allow us to stroke inside the width so we can
+ // adjust the sizes to fit if we're setting a stroke on the line
+ if (this.showStroke){
+ leftX += halfStroke;
+ rightX -= halfStroke;
+ top += halfStroke;
+ }
+
+ ctx.beginPath();
+
+ ctx.fillStyle = this.fillColor;
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ // It'd be nice to keep this class totally generic to any rectangle
+ // and simply specify which border to miss out.
+ ctx.moveTo(leftX, this.base);
+ ctx.lineTo(leftX, top);
+ ctx.lineTo(rightX, top);
+ ctx.lineTo(rightX, this.base);
+ ctx.fill();
+ if (this.showStroke){
+ ctx.stroke();
+ }
+ },
+ height : function(){
+ return this.base - this.y;
+ },
+ inRange : function(chartX,chartY){
+ return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
+ }
+ });
+
+ Chart.Tooltip = Chart.Element.extend({
+ draw : function(){
+
+ var ctx = this.chart.ctx;
+
+ ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
+
+ this.xAlign = "center";
+ this.yAlign = "above";
+
+ //Distance between the actual element.y position and the start of the tooltip caret
+ var caretPadding = 2;
+
+ var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
+ tooltipRectHeight = this.fontSize + 2*this.yPadding,
+ tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
+
+ if (this.x + tooltipWidth/2 >this.chart.width){
+ this.xAlign = "left";
+ } else if (this.x - tooltipWidth/2 < 0){
+ this.xAlign = "right";
+ }
+
+ if (this.y - tooltipHeight < 0){
+ this.yAlign = "below";
+ }
+
+
+ var tooltipX = this.x - tooltipWidth/2,
+ tooltipY = this.y - tooltipHeight;
+
+ ctx.fillStyle = this.fillColor;
+
+ switch(this.yAlign)
+ {
+ case "above":
+ //Draw a caret above the x/y
+ ctx.beginPath();
+ ctx.moveTo(this.x,this.y - caretPadding);
+ ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
+ ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
+ ctx.closePath();
+ ctx.fill();
+ break;
+ case "below":
+ tooltipY = this.y + caretPadding + this.caretHeight;
+ //Draw a caret below the x/y
+ ctx.beginPath();
+ ctx.moveTo(this.x, this.y + caretPadding);
+ ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
+ ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
+ ctx.closePath();
+ ctx.fill();
+ break;
+ }
+
+ switch(this.xAlign)
+ {
+ case "left":
+ tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
+ break;
+ case "right":
+ tooltipX = this.x - (this.cornerRadius + this.caretHeight);
+ break;
+ }
+
+ drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
+
+ ctx.fill();
+
+ ctx.fillStyle = this.textColor;
+ ctx.textAlign = "center";
+ ctx.textBaseline = "middle";
+ ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
+ }
+ });
+
+ Chart.MultiTooltip = Chart.Element.extend({
+ initialize : function(){
+ this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
+
+ this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
+
+ this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;
+
+ this.ctx.font = this.titleFont;
+
+ var titleWidth = this.ctx.measureText(this.title).width,
+ //Label has a legend square as well so account for this.
+ labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
+ longestTextWidth = max([labelWidth,titleWidth]);
+
+ this.width = longestTextWidth + (this.xPadding*2);
+
+
+ var halfHeight = this.height/2;
+
+ //Check to ensure the height will fit on the canvas
+ //The three is to buffer form the very
+ if (this.y - halfHeight < 0 ){
+ this.y = halfHeight;
+ } else if (this.y + halfHeight > this.chart.height){
+ this.y = this.chart.height - halfHeight;
+ }
+
+ //Decide whether to align left or right based on position on canvas
+ if (this.x > this.chart.width/2){
+ this.x -= this.xOffset + this.width;
+ } else {
+ this.x += this.xOffset;
+ }
+
+
+ },
+ getLineHeight : function(index){
+ var baseLineHeight = this.y - (this.height/2) + this.yPadding,
+ afterTitleIndex = index-1;
+
+ //If the index is zero, we're getting the title
+ if (index === 0){
+ return baseLineHeight + this.titleFontSize/2;
+ } else{
+ return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
+ }
+
+ },
+ draw : function(){
+ drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
+ var ctx = this.ctx;
+ ctx.fillStyle = this.fillColor;
+ ctx.fill();
+ ctx.closePath();
+
+ ctx.textAlign = "left";
+ ctx.textBaseline = "middle";
+ ctx.fillStyle = this.titleTextColor;
+ ctx.font = this.titleFont;
+
+ ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
+
+ ctx.font = this.font;
+ helpers.each(this.labels,function(label,index){
+ ctx.fillStyle = this.textColor;
+ ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
+
+ //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
+ //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+ //Instead we'll make a white filled block to put the legendColour palette over.
+
+ ctx.fillStyle = this.legendColorBackground;
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+
+ ctx.fillStyle = this.legendColors[index].fill;
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+
+
+ },this);
+ }
+ });
+
+ Chart.Scale = Chart.Element.extend({
+ initialize : function(){
+ this.fit();
+ },
+ buildYLabels : function(){
+ this.yLabels = [];
+
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
+
+ for (var i=0; i<=this.steps; i++){
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
+ }
+ this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
+ },
+ addXLabel : function(label){
+ this.xLabels.push(label);
+ this.valuesCount++;
+ this.fit();
+ },
+ removeXLabel : function(){
+ this.xLabels.shift();
+ this.valuesCount--;
+ this.fit();
+ },
+ // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
+ fit: function(){
+ // First we need the width of the yLabels, assuming the xLabels aren't rotated
+
+ // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
+ this.startPoint = (this.display) ? this.fontSize : 0;
+ this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
+
+ // Apply padding settings to the start and end point.
+ this.startPoint += this.padding;
+ this.endPoint -= this.padding;
+
+ // Cache the starting height, so can determine if we need to recalculate the scale yAxis
+ var cachedHeight = this.endPoint - this.startPoint,
+ cachedYLabelWidth;
+
+ // Build the current yLabels so we have an idea of what size they'll be to start
+ /*
+ * This sets what is returned from calculateScaleRange as static properties of this class:
+ *
+ this.steps;
+ this.stepValue;
+ this.min;
+ this.max;
+ *
+ */
+ this.calculateYRange(cachedHeight);
+
+ // With these properties set we can now build the array of yLabels
+ // and also the width of the largest yLabel
+ this.buildYLabels();
+
+ this.calculateXLabelRotation();
+
+ while((cachedHeight > this.endPoint - this.startPoint)){
+ cachedHeight = this.endPoint - this.startPoint;
+ cachedYLabelWidth = this.yLabelWidth;
+
+ this.calculateYRange(cachedHeight);
+ this.buildYLabels();
+
+ // Only go through the xLabel loop again if the yLabel width has changed
+ if (cachedYLabelWidth < this.yLabelWidth){
+ this.calculateXLabelRotation();
+ }
+ }
+
+ },
+ calculateXLabelRotation : function(){
+ //Get the width of each grid by calculating the difference
+ //between x offsets between 0 and 1.
+
+ this.ctx.font = this.font;
+
+ var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
+ lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
+ firstRotated,
+ lastRotated;
+
+
+ this.xScalePaddingRight = lastWidth/2 + 3;
+ this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
+
+ this.xLabelRotation = 0;
+ if (this.display){
+ var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
+ cosRotation,
+ firstRotatedWidth;
+ this.xLabelWidth = originalLabelWidth;
+ //Allow 3 pixels x2 padding either side for label readability
+ var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
+
+ //Max label rotate should be 90 - also act as a loop counter
+ while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
+ cosRotation = Math.cos(toRadians(this.xLabelRotation));
+
+ firstRotated = cosRotation * firstWidth;
+ lastRotated = cosRotation * lastWidth;
+
+ // We're right aligning the text now.
+ if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
+ this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
+ }
+ this.xScalePaddingRight = this.fontSize/2;
+
+
+ this.xLabelRotation++;
+ this.xLabelWidth = cosRotation * originalLabelWidth;
+
+ }
+ if (this.xLabelRotation > 0){
+ this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
+ }
+ }
+ else{
+ this.xLabelWidth = 0;
+ this.xScalePaddingRight = this.padding;
+ this.xScalePaddingLeft = this.padding;
+ }
+
+ },
+ // Needs to be overidden in each Chart type
+ // Otherwise we need to pass all the data into the scale class
+ calculateYRange: noop,
+ drawingArea: function(){
+ return this.startPoint - this.endPoint;
+ },
+ calculateY : function(value){
+ var scalingFactor = this.drawingArea() / (this.min - this.max);
+ return this.endPoint - (scalingFactor * (value - this.min));
+ },
+ calculateX : function(index){
+ var isRotated = (this.xLabelRotation > 0),
+ // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
+ innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
+ valueWidth = innerWidth/(this.valuesCount - ((this.offsetGridLines) ? 0 : 1)),
+ valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
+
+ if (this.offsetGridLines){
+ valueOffset += (valueWidth/2);
+ }
+
+ return Math.round(valueOffset);
+ },
+ update : function(newProps){
+ helpers.extend(this, newProps);
+ this.fit();
+ },
+ draw : function(){
+ var ctx = this.ctx,
+ yLabelGap = (this.endPoint - this.startPoint) / this.steps,
+ xStart = Math.round(this.xScalePaddingLeft);
+ if (this.display){
+ ctx.fillStyle = this.textColor;
+ ctx.font = this.font;
+ each(this.yLabels,function(labelString,index){
+ var yLabelCenter = this.endPoint - (yLabelGap * index),
+ linePositionY = Math.round(yLabelCenter);
+
+ ctx.textAlign = "right";
+ ctx.textBaseline = "middle";
+ if (this.showLabels){
+ ctx.fillText(labelString,xStart - 10,yLabelCenter);
+ }
+ ctx.beginPath();
+ if (index > 0){
+ // This is a grid line in the centre, so drop that
+ ctx.lineWidth = this.gridLineWidth;
+ ctx.strokeStyle = this.gridLineColor;
+ } else {
+ // This is the first line on the scale
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ }
+
+ linePositionY += helpers.aliasPixel(ctx.lineWidth);
+
+ ctx.moveTo(xStart, linePositionY);
+ ctx.lineTo(this.width, linePositionY);
+ ctx.stroke();
+ ctx.closePath();
+
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ ctx.beginPath();
+ ctx.moveTo(xStart - 5, linePositionY);
+ ctx.lineTo(xStart, linePositionY);
+ ctx.stroke();
+ ctx.closePath();
+
+ },this);
+
+ each(this.xLabels,function(label,index){
+ var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
+ // Check to see if line/bar here and decide where to place the line
+ linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
+ isRotated = (this.xLabelRotation > 0);
+
+ ctx.beginPath();
+
+ if (index > 0){
+ // This is a grid line in the centre, so drop that
+ ctx.lineWidth = this.gridLineWidth;
+ ctx.strokeStyle = this.gridLineColor;
+ } else {
+ // This is the first line on the scale
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ }
+ ctx.moveTo(linePos,this.endPoint);
+ ctx.lineTo(linePos,this.startPoint - 3);
+ ctx.stroke();
+ ctx.closePath();
+
+
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+
+
+ // Small lines at the bottom of the base grid line
+ ctx.beginPath();
+ ctx.moveTo(linePos,this.endPoint);
+ ctx.lineTo(linePos,this.endPoint + 5);
+ ctx.stroke();
+ ctx.closePath();
+
+ ctx.save();
+ ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
+ ctx.rotate(toRadians(this.xLabelRotation)*-1);
+ ctx.font = this.font;
+ ctx.textAlign = (isRotated) ? "right" : "center";
+ ctx.textBaseline = (isRotated) ? "middle" : "top";
+ ctx.fillText(label, 0, 0);
+ ctx.restore();
+ },this);
+
+ }
+ }
+
+ });
+
+ Chart.RadialScale = Chart.Element.extend({
+ initialize: function(){
+ this.size = min([this.height, this.width]);
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
+ },
+ calculateCenterOffset: function(value){
+ // Take into account half font size + the yPadding of the top value
+ var scalingFactor = this.drawingArea / (this.max - this.min);
+
+ return (value - this.min) * scalingFactor;
+ },
+ update : function(){
+ if (!this.lineArc){
+ this.setScaleSize();
+ } else {
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
+ }
+ this.buildYLabels();
+ },
+ buildYLabels: function(){
+ this.yLabels = [];
+
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
+
+ for (var i=0; i<=this.steps; i++){
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
+ }
+ },
+ getCircumference : function(){
+ return ((Math.PI*2) / this.valuesCount);
+ },
+ setScaleSize: function(){
+ /*
+ * Right, this is really confusing and there is a lot of maths going on here
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
+ *
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
+ *
+ * Solution:
+ *
+ * We assume the radius of the polygon is half the size of the canvas at first
+ * at each index we check if the text overlaps.
+ *
+ * Where it does, we store that angle and that index.
+ *
+ * After finding the largest index and angle we calculate how much we need to remove
+ * from the shape radius to move the point inwards by that x.
+ *
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
+ * along with labels.
+ *
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
+ *
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
+ * and position it in the most space efficient manner
+ *
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
+ */
+
+
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
+ var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
+ pointPosition,
+ i,
+ textWidth,
+ halfTextWidth,
+ furthestRight = this.width,
+ furthestRightIndex,
+ furthestRightAngle,
+ furthestLeft = 0,
+ furthestLeftIndex,
+ furthestLeftAngle,
+ xProtrusionLeft,
+ xProtrusionRight,
+ radiusReductionRight,
+ radiusReductionLeft,
+ maxWidthRadius;
+ this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
+ for (i=0;i furthestRight) {
+ furthestRight = pointPosition.x + halfTextWidth;
+ furthestRightIndex = i;
+ }
+ if (pointPosition.x - halfTextWidth < furthestLeft) {
+ furthestLeft = pointPosition.x - halfTextWidth;
+ furthestLeftIndex = i;
+ }
+ }
+ else if (i < this.valuesCount/2) {
+ // Less than half the values means we'll left align the text
+ if (pointPosition.x + textWidth > furthestRight) {
+ furthestRight = pointPosition.x + textWidth;
+ furthestRightIndex = i;
+ }
+ }
+ else if (i > this.valuesCount/2){
+ // More than half the values means we'll right align the text
+ if (pointPosition.x - textWidth < furthestLeft) {
+ furthestLeft = pointPosition.x - textWidth;
+ furthestLeftIndex = i;
+ }
+ }
+ }
+
+ xProtrusionLeft = furthestLeft;
+
+ xProtrusionRight = Math.ceil(furthestRight - this.width);
+
+ furthestRightAngle = this.getIndexAngle(furthestRightIndex);
+
+ furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
+
+ radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
+
+ radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
+
+ // Ensure we actually need to reduce the size of the chart
+ radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
+ radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
+
+ this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
+
+ //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
+ this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
+
+ },
+ setCenterPoint: function(leftMovement, rightMovement){
+
+ var maxRight = this.width - rightMovement - this.drawingArea,
+ maxLeft = leftMovement + this.drawingArea;
+
+ this.xCenter = (maxLeft + maxRight)/2;
+ // Always vertically in the centre as the text height doesn't change
+ this.yCenter = (this.height/2);
+ },
+
+ getIndexAngle : function(index){
+ var angleMultiplier = (Math.PI * 2) / this.valuesCount;
+ // Start from the top instead of right, so remove a quarter of the circle
+
+ return index * angleMultiplier - (Math.PI/2);
+ },
+ getPointPosition : function(index, distanceFromCenter){
+ var thisAngle = this.getIndexAngle(index);
+ return {
+ x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
+ y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
+ };
+ },
+ draw: function(){
+ if (this.display){
+ var ctx = this.ctx;
+ each(this.yLabels, function(label, index){
+ // Don't draw a centre value
+ if (index > 0){
+ var yCenterOffset = index * (this.drawingArea/this.steps),
+ yHeight = this.yCenter - yCenterOffset,
+ pointPosition;
+
+ // Draw circular lines around the scale
+ if (this.lineWidth > 0){
+ ctx.strokeStyle = this.lineColor;
+ ctx.lineWidth = this.lineWidth;
+
+ if(this.lineArc){
+ ctx.beginPath();
+ ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
+ ctx.closePath();
+ ctx.stroke();
+ } else{
+ ctx.beginPath();
+ for (var i=0;i= 0; i--) {
+ if (this.angleLineWidth > 0){
+ var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
+ ctx.beginPath();
+ ctx.moveTo(this.xCenter, this.yCenter);
+ ctx.lineTo(outerPosition.x, outerPosition.y);
+ ctx.stroke();
+ ctx.closePath();
+ }
+ // Extra 3px out for some label spacing
+ var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
+ ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
+ ctx.fillStyle = this.pointLabelFontColor;
+
+ var labelsCount = this.labels.length,
+ halfLabelsCount = this.labels.length/2,
+ quarterLabelsCount = halfLabelsCount/2,
+ upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
+ exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
+ if (i === 0){
+ ctx.textAlign = 'center';
+ } else if(i === halfLabelsCount){
+ ctx.textAlign = 'center';
+ } else if (i < halfLabelsCount){
+ ctx.textAlign = 'left';
+ } else {
+ ctx.textAlign = 'right';
+ }
+
+ // Set the correct text baseline based on outer positioning
+ if (exactQuarter){
+ ctx.textBaseline = 'middle';
+ } else if (upperHalf){
+ ctx.textBaseline = 'bottom';
+ } else {
+ ctx.textBaseline = 'top';
+ }
+
+ ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
+ }
+ }
+ }
+ }
+ });
+
+ // Attach global event to resize each chart instance when the browser resizes
+ helpers.addEvent(window, "resize", (function(){
+ // Basic debounce of resize function so it doesn't hurt performance when resizing browser.
+ var timeout;
+ return function(){
+ clearTimeout(timeout);
+ timeout = setTimeout(function(){
+ each(Chart.instances,function(instance){
+ // If the responsive flag is set in the chart instance config
+ // Cascade the resize event down to the chart.
+ if (instance.options.responsive){
+ instance.resize(instance.render, true);
+ }
+ });
+ }, 50);
+ };
+ })());
+
+
+ if (amd) {
+ define(function(){
+ return Chart;
+ });
+ } else if (typeof module === 'object' && module.exports) {
+ module.exports = Chart;
+ }
+
+ root.Chart = Chart;
+
+ Chart.noConflict = function(){
+ root.Chart = previous;
+ return Chart;
+ };
+
+}).call(this);
+
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+
+ var defaultConfig = {
+ //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
+ scaleBeginAtZero : true,
+
+ //Boolean - Whether grid lines are shown across the chart
+ scaleShowGridLines : true,
+
+ //String - Colour of the grid lines
+ scaleGridLineColor : "rgba(0,0,0,.05)",
+
+ //Number - Width of the grid lines
+ scaleGridLineWidth : 1,
+
+ //Boolean - If there is a stroke on each bar
+ barShowStroke : true,
+
+ //Number - Pixel width of the bar stroke
+ barStrokeWidth : 2,
+
+ //Number - Spacing between each of the X value sets
+ barValueSpacing : 5,
+
+ //Number - Spacing between data sets within X values
+ barDatasetSpacing : 1,
+
+ //String - A legend template
+ legendTemplate : "
<%}%>'},initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx}),this.datasets=[],this.buildScale(t),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){var o;this.scale.animation||(o=this.scale.getPointPosition(n,this.scale.calculateCenterOffset(e))),s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,x:this.options.animation?this.scale.xCenter:o.x,y:this.options.animation?this.scale.yCenter:o.y,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this)},this),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=e.getRelativePosition(t),s=e.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},i),n=2*Math.PI/this.scale.valuesCount,o=Math.round((s.angle-1.5*Math.PI)/n),a=[];return(o>=this.scale.valuesCount||0>o)&&(o=0),s.distance<=this.scale.drawingArea&&e.each(this.datasets,function(t){a.push(t.points[o])}),a},buildScale:function(t){this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,angleLineColor:this.options.angleLineColor,angleLineWidth:this.options.angleShowLineOut?this.options.angleLineWidth:0,pointLabelFontColor:this.options.pointLabelFontColor,pointLabelFontSize:this.options.pointLabelFontSize,pointLabelFontFamily:this.options.pointLabelFontFamily,pointLabelFontStyle:this.options.pointLabelFontStyle,height:this.chart.height,width:this.chart.width,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,labels:t.labels,valuesCount:t.datasets[0].data.length}),this.scale.setScaleSize(),this.updateScaleRange(t.datasets),this.scale.buildYLabels()},updateScaleRange:function(t){var i=function(){var i=[];return e.each(t,function(t){t.data?i=i.concat(t.data):e.each(t.points,function(t){i.push(t.value)})}),i}(),s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s)},addData:function(t,i){this.scale.valuesCount++,e.each(t,function(t,e){var s=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:s.x,y:s.y,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.labels.push(i),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),e.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2}),this.updateScaleRange(this.datasets),this.scale.setScaleSize(),this.scale.buildYLabels()},draw:function(t){var i=t||1,s=this.chart.ctx;this.clear(),this.scale.draw(),e.each(this.datasets,function(t){e.each(t.points,function(t,e){t.hasValue()&&t.transition(this.scale.getPointPosition(e,this.scale.calculateCenterOffset(t.value)),i)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(t.points,function(t,i){0===i?s.moveTo(t.x,t.y):s.lineTo(t.x,t.y)},this),s.closePath(),s.stroke(),s.fillStyle=t.fillColor,s.fill(),e.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this);
\ No newline at end of file
diff --git a/admin/js/main.js b/admin/js/main.js
new file mode 100644
index 0000000..9afc4b9
--- /dev/null
+++ b/admin/js/main.js
@@ -0,0 +1,56 @@
+// Permet d'ajouter un champ dans le formulaire d'ajout de candidats au CS
+// Limite à 20 le nombre de champ maximum
+function add_candidat(i) {
+ var i2 = i + 1;
+ document.getElementById('add_'+i).innerHTML = '
';
+ document.getElementById('add_'+i).innerHTML += (i <= 20) ? '
a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,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"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window);
diff --git a/admin/js/vendor/modernizr-2.6.2.min.js b/admin/js/vendor/modernizr-2.6.2.min.js
new file mode 100644
index 0000000..f65d479
--- /dev/null
+++ b/admin/js/vendor/modernizr-2.6.2.min.js
@@ -0,0 +1,4 @@
+/* Modernizr 2.6.2 (Custom Build) | MIT & BSD
+ * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load
+ */
+;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f
+* Version 2.0.5 - built Thu Aug 28 2014 11:33:36
+* MIT Licensed
+*
+*/
+!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){"undefined"==typeof a&&"undefined"!=typeof window.jQuery&&(a=window.jQuery);var b={attr:function(a,b,c){var d,e={},f=this.msieversion(),g=new RegExp("^"+b,"i");if("undefined"==typeof a||"undefined"==typeof a[0])return{};for(var h in a[0].attributes)if(d=a[0].attributes[h],"undefined"!=typeof d&&null!==d&&(!f||f>=8||d.specified)&&g.test(d.name)){if("undefined"!=typeof c&&new RegExp(c+"$","i").test(d.name))return!0;e[this.camelize(d.name.replace(b,""))]=this.deserializeValue(d.value)}return"undefined"==typeof c?e:!1},setAttr:function(a,b,c,d){a[0].setAttribute(this.dasherize(b+c),String(d))},get:function(a,b){for(var c=0,d=(b||"").split(".");this.isObject(a)||this.isArray(a);)if(a=a[d[c++]],c===d.length)return a;return void 0},hash:function(a){return String(Math.random()).substring(2,a?a+2:9)},isArray:function(a){return"[object Array]"===Object.prototype.toString.call(a)},isObject:function(a){return a===Object(a)},deserializeValue:function(b){var c;try{return b?"true"==b||("false"==b?!1:"null"==b?null:isNaN(c=Number(b))?/^[\[\{]/.test(b)?a.parseJSON(b):b:c):b}catch(d){return b}},camelize:function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},dasherize:function(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()},msieversion:function(){var a=window.navigator.userAgent,b=a.indexOf("MSIE ");return b>0||navigator.userAgent.match(/Trident.*rv\:11\./)?parseInt(a.substring(b+5,a.indexOf(".",b)),10):0}},c={namespace:"data-parsley-",inputs:"input, textarea, select",excluded:"input[type=button], input[type=submit], input[type=reset], input[type=hidden]",priorityEnabled:!0,uiEnabled:!0,validationThreshold:3,focus:"first",trigger:!1,errorClass:"parsley-error",successClass:"parsley-success",classHandler:function(){},errorsContainer:function(){},errorsWrapper:'
',errorTemplate:""},d=function(){};d.prototype={asyncSupport:!1,actualizeOptions:function(){return this.options=this.OptionsFactory.get(this),this},validateThroughValidator:function(a,b,c){return window.ParsleyValidator.validate.apply(window.ParsleyValidator,[a,b,c])},subscribe:function(b,c){return a.listenTo(this,b.toLowerCase(),c),this},unsubscribe:function(b){return a.unsubscribeTo(this,b.toLowerCase()),this},reset:function(){if("ParsleyForm"!==this.__class__)return a.emit("parsley:field:reset",this);for(var b=0;b=0;l--)"Required"!==k[l].__class__||(i=k[l].requiresValidation(b));if(this.has(h,a)||this.options.strict||i)try{this.has(h,this.options.strict||i?a:void 0)||(new e).HaveProperty(h).validate(a),c=this._check(h,a[h],b),(g(c)&&c.length>0||!g(c)&&!f(c))&&(d[h]=c)}catch(m){d[h]=m}}return f(d)?!0:d},add:function(a,b){if(b instanceof e||g(b)&&b[0]instanceof e)return this.nodes[a]=b,this;if("object"==typeof b&&!g(b))return this.nodes[a]=b instanceof c?b:new c(b),this;throw new Error("Should give an Assert, an Asserts array, a Constraint",b)},has:function(a,b){return b="undefined"!=typeof b?b:this.nodes,"undefined"!=typeof b[a]},get:function(a,b){return this.has(a)?this.nodes[a]:b||null},remove:function(a){var b=[];for(var c in this.nodes)c!==a&&(b[c]=this.nodes[c]);return this.nodes=b,this},_bootstrap:function(a){if(a instanceof c)return this.nodes=a.nodes;for(var b in a)this.add(b,a[b])},_check:function(a,b,d){if(this.nodes[a]instanceof e)return this._checkAsserts(b,[this.nodes[a]],d);if(g(this.nodes[a]))return this._checkAsserts(b,this.nodes[a],d);if(this.nodes[a]instanceof c)return this.nodes[a].check(b,d);throw new Error("Invalid node",this.nodes[a])},_checkAsserts:function(a,b,c){for(var d,e=[],f=0;f0},addGroup:function(a){return g(a)?this.addGroups(a):(this.hasGroup(a)||this.groups.push(a),this)},removeGroup:function(a){for(var b=[],c=0;c=a)throw new d(this,a,{threshold:this.threshold});return!0},this},GreaterThanOrEqual:function(a){if(this.__class__="GreaterThanOrEqual","undefined"==typeof a)throw new Error("Should give a threshold value");return this.threshold=a,this.validate=function(a){if(""===a||isNaN(Number(a)))throw new d(this,a,{value:b.errorCode.must_be_a_number});if(this.threshold>a)throw new d(this,a,{threshold:this.threshold});return!0},this},InstanceOf:function(a){if(this.__class__="InstanceOf","undefined"==typeof a)throw new Error("InstanceOf must be instanciated with a value");return this.classRef=a,this.validate=function(a){if(!0!=a instanceof this.classRef)throw new d(this,a,{classRef:this.classRef});return!0},this},Length:function(a){if(this.__class__="Length",!a.min&&!a.max)throw new Error("Lenth assert must be instanciated with a { min: x, max: y } object");return this.min=a.min,this.max=a.max,this.validate=function(a){if("string"!=typeof a&&!g(a))throw new d(this,a,{value:b.errorCode.must_be_a_string_or_array});if("undefined"!=typeof this.min&&this.min===this.max&&a.length!==this.min)throw new d(this,a,{min:this.min,max:this.max});if("undefined"!=typeof this.max&&a.length>this.max)throw new d(this,a,{max:this.max});if("undefined"!=typeof this.min&&a.length>>0;if(0===c)return-1;var d=0;if(arguments.length>1&&(d=Number(arguments[1]),d!=d?d=0:0!==d&&1/0!=d&&d!=-1/0&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1});var f=function(a){for(var b in a)return!1;return!0},g=function(a){return"[object Array]"===Object.prototype.toString.call(a)};return"function"==typeof define&&define.amd?define("vendors/validator.js/dist/validator",[],function(){return a}):"undefined"!=typeof module&&module.exports?module.exports=a:window["undefined"!=typeof validatorjs_ns?validatorjs_ns:"Validator"]=a,a}();e="undefined"!=typeof e?e:"undefined"!=typeof module?module.exports:null;var f=function(a,b){this.__class__="ParsleyValidator",this.Validator=e,this.locale="en",this.init(a||{},b||{})};f.prototype={init:function(b,c){this.catalog=c;for(var d in b)this.addValidator(d,b[d].fn,b[d].priority,b[d].requirementsTransformer);a.emit("parsley:validator:init")},setLocale:function(a){if("undefined"==typeof this.catalog[a])throw new Error(a+" is not available in the catalog");return this.locale=a,this},addCatalog:function(a,b,c){return"object"==typeof b&&(this.catalog[a]=b),!0===c?this.setLocale(a):this},addMessage:function(a,b,c){return"undefined"==typeof this.catalog[a]&&(this.catalog[a]={}),this.catalog[a][b.toLowerCase()]=c,this},validate:function(){return(new this.Validator.Validator).validate.apply(new e.Validator,arguments)},addValidator:function(b,c,d,f){return this.validators[b.toLowerCase()]=function(b){return a.extend((new e.Assert).Callback(c,b),{priority:d,requirementsTransformer:f})},this},updateValidator:function(a,b,c,d){return this.addValidator(a,b,c,d)},removeValidator:function(a){return delete this.validators[a],this},getErrorMessage:function(a){var b;return b="type"===a.name?this.catalog[this.locale][a.name][a.requirements]:this.formatMessage(this.catalog[this.locale][a.name],a.requirements),""!==b?b:this.catalog[this.locale].defaultMessage},formatMessage:function(a,b){if("object"==typeof b){for(var c in b)a=this.formatMessage(a,b[c]);return a}return"string"==typeof a?a.replace(new RegExp("%s","i"),b):""},validators:{notblank:function(){return a.extend((new e.Assert).NotBlank(),{priority:2})},required:function(){return a.extend((new e.Assert).Required(),{priority:512})},type:function(b){var c;switch(b){case"email":c=(new e.Assert).Email();break;case"range":case"number":c=(new e.Assert).Regexp("^-?(?:\\d+|\\d{1,3}(?:,\\d{3})+)?(?:\\.\\d+)?$");break;case"integer":c=(new e.Assert).Regexp("^-?\\d+$");break;case"digits":c=(new e.Assert).Regexp("^\\d+$");break;case"alphanum":c=(new e.Assert).Regexp("^\\w+$","i");break;case"url":c=(new e.Assert).Regexp("(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,4}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)","i");break;default:throw new Error("validator type `"+b+"` is not supported")}return a.extend(c,{priority:256})},pattern:function(b){var c="";return/^\/.*\/(?:[gimy]*)$/.test(b)&&(c=b.replace(/.*\/([gimy]*)$/,"$1"),b=b.replace(new RegExp("^/(.*?)/"+c+"$"),"$1")),a.extend((new e.Assert).Regexp(b,c),{priority:64})},minlength:function(b){return a.extend((new e.Assert).Length({min:b}),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},maxlength:function(b){return a.extend((new e.Assert).Length({max:b}),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},length:function(b){return a.extend((new e.Assert).Length({min:b[0],max:b[1]}),{priority:32})},mincheck:function(a){return this.minlength(a)},maxcheck:function(a){return this.maxlength(a)},check:function(a){return this.length(a)},min:function(b){return a.extend((new e.Assert).GreaterThanOrEqual(b),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},max:function(b){return a.extend((new e.Assert).LessThanOrEqual(b),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},range:function(b){return a.extend((new e.Assert).Range(b[0],b[1]),{priority:32,requirementsTransformer:function(){for(var a=0;a0?this._errorClass(a):this._resetClass(a)},manageErrorsMessages:function(b,c){if("undefined"==typeof b.options.errorsMessagesDisabled){if("undefined"!=typeof b.options.errorMessage)return c.added.length||c.kept.length?(0===b._ui.$errorsWrapper.find(".parsley-custom-error-message").length&&b._ui.$errorsWrapper.append(a(b.options.errorTemplate).addClass("parsley-custom-error-message")),b._ui.$errorsWrapper.addClass("filled").find(".parsley-custom-error-message").html(b.options.errorMessage)):b._ui.$errorsWrapper.removeClass("filled").find(".parsley-custom-error-message").remove();for(var d=0;d0&&"undefined"==typeof a.fields[b].options.noFocus){if("first"===a.options.focus)return a._focusedField=a.fields[b].$element,a._focusedField.focus();a._focusedField=a.fields[b].$element}return null===a._focusedField?null:a._focusedField.focus()},_getErrorMessage:function(a,b){var c=b.name+"Message";return"undefined"!=typeof a.options[c]?window.ParsleyValidator.formatMessage(a.options[c],b.requirements):window.ParsleyValidator.getErrorMessage(b)},_diff:function(a,b,c){for(var d=[],e=[],f=0;f0&&this.validationResult&&(this.validationResult=!1));return a.emit("parsley:form:validated",this),this.validationResult},isValid:function(a,b){this._refreshFields();for(var c=0;c1){var c=[];return this.each(function(){c.push(a(this).parsley(b))}),c}return a(this).length?new o(this,b):void(window.console&&window.console.warn&&window.console.warn("You must bind Parsley on an existing element."))},window.ParsleyUI="function"==typeof b.get(window,"ParsleyConfig.ParsleyUI")?(new window.ParsleyConfig.ParsleyUI).listen():(new g).listen(),"undefined"==typeof window.ParsleyExtend&&(window.ParsleyExtend={}),"undefined"==typeof window.ParsleyConfig&&(window.ParsleyConfig={}),window.Parsley=window.psly=o,window.ParsleyUtils=b,window.ParsleyValidator=new f(window.ParsleyConfig.validators,window.ParsleyConfig.i18n),!1!==b.get(window,"ParsleyConfig.autoBind")&&a(document).ready(function(){a("[data-parsley-validate]").length&&a("[data-parsley-validate]").parsley()})});
\ No newline at end of file
diff --git a/admin/listecooperateurs.php b/admin/listecooperateurs.php
new file mode 100644
index 0000000..a72cd38
--- /dev/null
+++ b/admin/listecooperateurs.php
@@ -0,0 +1,70 @@
+query('SELECT reference, email, nom, prenom FROM clients');
+ // on paramètre le type de fichier, l'encodage, et le nom du fichier
+ header('Content-Type: text/csv; charset=utf-8');
+ header('Content-Disposition: attachment; filename=ListeDesCooperateurs.csv');
+
+ // on prépare un fichier lié au flux de sortie
+ $output = fopen('php://output', 'w');
+
+ // on paramètre le nom des colonnes
+ fputcsv($output, array('Numéro', 'EMail', 'Nom', 'Prénom'));
+
+ // on boucle et on alimente le fichier
+ while ($results = $query->fetch(PDO::FETCH_ASSOC)) fputcsv($output, $results);
+ exit; // On évite d'ajouter le code html de la page au fichier
+}
+?>
+
+
+
+
+
+
+
+
+
+
+
Vous êtes authentifié sous l'utilisateur :
+
+ Se déconnecter
+
+
+
Listes des coopérateurs
+
+ query('SELECT reference, email, nom, prenom FROM clients');
+ // Si le retour est vide, on affiche un petit message
+ if ($query->rowCount() > 0) {
+ echo '
Nombre de coopérateurs appelés à voter : '.$query->rowCount().'
';
+ echo '';
+ // On affiche les resultats
+ while ($results = $query->fetch(PDO::FETCH_OBJ)) {
+ echo '
';
+ }
+ }
+ //Clore la requête
+ $query->closeCursor();
+ $query = NULL;
+ ?>
+
+
+
+
+
+
+
diff --git a/admin/listenonvotants.php b/admin/listenonvotants.php
new file mode 100644
index 0000000..c2c75f4
--- /dev/null
+++ b/admin/listenonvotants.php
@@ -0,0 +1,71 @@
+query('SELECT reference, email, nom, prenom FROM clients WHERE vote = 0');
+ // on paramètre le type de fichier, l'encodage, et le nom du fichier
+ header('Content-Type: text/csv; charset=utf-8');
+ header('Content-Disposition: attachment; filename=ListeDesCooperateurs.csv');
+
+ // on prépare un fichier lié au flux de sortie
+ $output = fopen('php://output', 'w');
+
+ // on paramètre le nom des colonnes
+ fputcsv($output, array('Numéro', 'EMail', 'Nom', 'Prénom'));
+
+ // on boucle et on alimente le fichier
+ while ($results = $query->fetch(PDO::FETCH_ASSOC)) fputcsv($output, $results);
+ exit; // On évite d'ajouter le code html de la page au fichier
+}
+?>
+
+
+
+
+
+
+
+
+
+
+
Vous êtes authentifié sous l'utilisateur :
+
+ Se déconnecter
+
+
+
Coopérateurs n'ayant pas voté
+
+ query('SELECT reference, email, nom, prenom FROM clients WHERE vote = 0');
+ // Si le retour est vide, on affiche un petit message
+ if ($query->rowCount() > 0) {
+ echo '
Nombre de coopérateurs n\'ayant pas encore voté : '.$query->rowCount().'
';
+ echo '';
+ // On affiche les resultats
+ while ($results = $query->fetch(PDO::FETCH_OBJ)) {
+ echo "
";
+ }
+ } else { echo "Tous les coopérateurs ont voté !";}
+ //Clore la requête
+ $query->closeCursor();
+ $query = NULL
+ ?>
+
+
+
+
+
+
+
diff --git a/admin/listevotants.php b/admin/listevotants.php
new file mode 100644
index 0000000..9dff7c2
--- /dev/null
+++ b/admin/listevotants.php
@@ -0,0 +1,71 @@
+query('SELECT reference, email, nom, prenom FROM clients WHERE vote = 1');
+ // on paramètre le type de fichier, l'encodage, et le nom du fichier
+ header('Content-Type: text/csv; charset=utf-8');
+ header('Content-Disposition: attachment; filename=ListeDesCooperateurs.csv');
+
+ // on prépare un fichier lié au flux de sortie
+ $output = fopen('php://output', 'w');
+
+ // on paramètre le nom des colonnes
+ fputcsv($output, array('Numéro', 'EMail', 'Nom', 'Prénom'));
+
+ // on boucle et on alimente le fichier
+ while ($results = $query->fetch(PDO::FETCH_ASSOC)) fputcsv($output, $results);
+ exit; // On évite d'ajouter le code html de la page au fichier
+}
+?>
+
+
+
+
+
+
+
+
+
+
+
Vous êtes authentifié sous l'utilisateur :
+
+ Se déconnecter
+
+
+
Coopérateurs ayant voté
+
+ query('SELECT reference, email, nom, prenom FROM clients WHERE vote = 1');
+ // Si le retour est vide, on affiche un petit message
+ if ($query->rowCount() > 0) {
+ echo '
Nombre de coopérateurs ayant déjà voté : '.$query->rowCount().'
';
+ echo '';
+ // On affiche les resultats
+ while ($results = $query->fetch(PDO::FETCH_OBJ)) {
+ echo "
';}
+ //Clore la requête
+ $query->closeCursor();
+ $query = NULL
+ ?>
+
+
+
+
+
+
+
diff --git a/admin/resultats.php b/admin/resultats.php
new file mode 100644
index 0000000..a33e91a
--- /dev/null
+++ b/admin/resultats.php
@@ -0,0 +1,215 @@
+prepare($sql);
+// On regarde le nombre de résolution d'AGO qui sera important par la suite pour créer la page de résultats
+if ($ago) {
+ $option = 'resolution_ago_nb';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ $stmt->execute();
+ $results = $stmt->fetch();
+ $resolution_ago_nb = $results['option_value'];
+}
+// On regarde le nombre de résolution d'AGE qui sera important par la suite pour créer la page de résultats
+if ($age) {
+ $option = 'resolution_age_nb';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ $stmt->execute();
+ $results = $stmt->fetch();
+ $resolution_age_nb = $results['option_value'];
+}
+// On compte le total de vote pour la résolution
+$sql ="SELECT COUNT(*) AS nb FROM resultats";
+$stmt = $bdd->query($sql);
+$nb = $stmt->fetch();
+$r_all = $nb['nb'];
+
+// On compte pour chaque résolution d'AGO le nombre de oui, non et d'abstention
+if ($ago) {
+ for ($i = 1; $i <= $resolution_ago_nb; $i++) {
+ // On compte pour chaque choix possible le nombre de vote
+ $sql ="SELECT COUNT(*) FROM resultats WHERE resolution_ago_".$i." = ? GROUP BY resolution_ago_".$i;
+ $stmt = $bdd->prepare($sql);
+ $stmt->execute(array('Oui'));
+ ${'rago'.$i.'_oui'} = $stmt->fetchColumn();
+ $stmt->execute(array('Non'));
+ ${'rago'.$i.'_non'} = $stmt->fetchColumn();
+ $stmt->execute(array('Abstention'));
+ ${'rago'.$i.'_abst'} = $stmt->fetchColumn();
+ // Génération des graphs avec nos résultats
+ $chart = new HorizontalBarChart(600, 200);
+ $dataSet = new XYDataSet();
+ $dataSet->addPoint(new Point("Abstention", ${'rago'.$i.'_abst'}));
+ $dataSet->addPoint(new Point("Non", ${'rago'.$i.'_non'}));
+ $dataSet->addPoint(new Point("Oui", ${'rago'.$i.'_oui'}));
+ $chart->setDataSet($dataSet);
+ $chart->setTitle("Résultat pour la résolution d'AGO n°".$i);
+ $chart->render("img/resolution_ago_".$i.".png");
+ }
+}
+// On compte pour chaque résolution d'AGE le nombre de oui, non et d'abstention
+if ($age) {
+ for ($i = 1; $i <= $resolution_age_nb; $i++) {
+ // On compte pour chaque choix possible le nombre de vote
+ $sql ="SELECT COUNT(*) FROM resultats WHERE resolution_age_".$i." = ? GROUP BY resolution_age_".$i;
+ $stmt = $bdd->prepare($sql);
+ $stmt->execute(array('Oui'));
+ ${'rage'.$i.'_oui'} = $stmt->fetchColumn();
+ $stmt->execute(array('Non'));
+ ${'rage'.$i.'_non'} = $stmt->fetchColumn();
+ $stmt->execute(array('Abstention'));
+ ${'rage'.$i.'_abst'} = $stmt->fetchColumn();
+ // Génération des graphs avec nos résultats
+ $chart = new HorizontalBarChart(600, 200);
+ $dataSet = new XYDataSet();
+ $dataSet->addPoint(new Point("Abstention", ${'rage'.$i.'_abst'}));
+ $dataSet->addPoint(new Point("Non", ${'rage'.$i.'_non'}));
+ $dataSet->addPoint(new Point("Oui", ${'rage'.$i.'_oui'}));
+ $chart->setDataSet($dataSet);
+ $chart->setTitle("Résultat pour la résolution d'AGE n°".$i);
+ $chart->render("img/resolution_age_".$i.".png");
+ }
+}
+// On compte le total de vote pour le CS
+if ($cs) {
+ $sql = "SELECT vote_cs FROM resultats";
+ $stmt = $bdd->prepare($sql);
+ $stmt->execute();
+ $nb_vote = $stmt->rowCount();
+ // On compte le nombre d'absention au vote pour le CS
+ $sql = "SELECT vote_cs FROM resultats WHERE vote_cs = :option";
+ $stmt = $bdd->prepare($sql);
+ $option = 'Abstention';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ $stmt->execute();
+ $nb_abstention_cs = $stmt->rowCount();
+
+ // On prépare la création du graph pour le CS
+ $chart = new PieChart(600, 400);
+ $dataSet = new XYDataSet();
+ // On va chercher la liste des candidats au CS
+ $sql = "SELECT option_name, option_value FROM admin WHERE option_name = :option";
+ $stmt = $bdd->prepare($sql);
+ $option = 'cs_candidats';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ // Exécution de la requête
+ $stmt->execute();
+ $results = $stmt->fetch();
+ $cs_candidats = $results['option_value'];
+ $cs_candidats = explode(",",$cs_candidats);
+}
+?>
+
+
+
+
+
+
+
+
+
+
+
Vous êtes authentifié sous l'utilisateur :
+
+ Se déconnecter
+
+
+
Résultats
+
+ Nombre de votes total :
+
+
+
+
+
+
Résolutions de l'Assemblée Générale Ordinaire
+
+
Résolution
+
+ Oui :
+ Non :
+ Abstention :
+
+
+
+
+
+
+
+
+
Résolutions de l'Assemblée Générale Extraordinaire
+
+
Résolution
+
+ Oui :
+ Non :
+ Abstention :
+
+
+
+
+
+
+
+
Élection des membres du CS
+
prepare($sql);
+ $stmt->execute();
+ // On fait le tour des résultats à chaque candidat, et on incrémente le compteur de vote à chaque vote pour ce candidat
+ while ($results = $stmt->fetch()) {
+ foreach($results AS $ligne) {
+ $ligne = explode(",",$ligne);
+ if (in_array("$element", $ligne)) {${'vote_'.$element2}++;}
+ }
+ }
+ ${'vote_'.$element2} = ${'vote_'.$element2} / 2;
+ // Le tour des résultats est terminé, on génére un point sur le graph avec le nom et le nombre de vote pour ce candidat
+ $dataSet->addPoint(new Point($element, ${'vote_'.$element2}));
+ // On ajoute une ligne texte en plus du graph
+ echo ''.$element.' : '.${'vote_'.$element2}.' voix';
+ }
+
+ //Clore la requête
+ $stmt->closeCursor();
+ $stmt = NULL;
+ // On finalise le graph CS
+ $chart->setDataSet($dataSet);
+ $chart->setTitle("Résultat pour l'élection des membres du CS");
+ $chart->render("img/vote_cs2.png");
+ ?>
+
+
diff --git a/crossdomain.xml b/crossdomain.xml
new file mode 100644
index 0000000..29a035d
--- /dev/null
+++ b/crossdomain.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/css/main.css b/css/main.css
new file mode 100644
index 0000000..f1d16ab
--- /dev/null
+++ b/css/main.css
@@ -0,0 +1,500 @@
+/*! HTML5 Boilerplate v4.3.0 | MIT License | http://h5bp.com/ */
+
+/*
+ * What follows is the result of much research on cross-browser styling.
+ * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
+ * Kroc Camen, and the H5BP dev community and team.
+ */
+
+/* ==========================================================================
+ Base styles: opinionated defaults
+ ========================================================================== */
+
+html,
+button,
+input,
+select,
+textarea {
+ color: #222;
+}
+
+html {
+ line-height: 1.4;
+ font: 1em/1.4em "Droid Sans",Helvetica,Arial,sans-serif;
+}
+
+/*
+ * Remove text-shadow in selection highlight: h5bp.com/i
+ * These selection rule sets have to be separate.
+ * Customize the background color to match your design.
+ */
+
+::-moz-selection {
+ background: #b3d4fc;
+ text-shadow: none;
+}
+
+::selection {
+ background: #b3d4fc;
+ text-shadow: none;
+}
+
+/*
+ * A better looking default horizontal rule
+ */
+
+hr {
+ display: block;
+ height: 1px;
+ border: 0;
+ border-top: 1px solid #ccc;
+ margin: 1em 0;
+ padding: 0;
+}
+
+/*
+ * Remove the gap between images, videos, audio and canvas and the bottom of
+ * their containers: h5bp.com/i/440
+ */
+
+audio,
+canvas,
+img,
+video {
+ vertical-align: middle;
+}
+
+/*
+ * Remove default fieldset styles.
+ */
+
+fieldset {
+ border: 0;
+ margin: 0;
+ padding: 0;
+}
+
+/*
+ * Allow only vertical resizing of textareas.
+ */
+
+textarea {
+ resize: vertical;
+}
+
+/* ==========================================================================
+ Browse Happy prompt
+ ========================================================================== */
+
+.browsehappy {
+ margin: 0.2em 0;
+ background: #ccc;
+ color: #000;
+ padding: 0.2em 0;
+}
+
+/* ==========================================================================
+ Author's custom styles
+ ========================================================================== */
+
+.mt1 {margin-top:1em;}
+.mr1 {margin-right:1em;}
+.mb1 {margin-bottom:1em;}
+.ml1 {margin-left:1em;}
+.mt2 {margin-top:2em;}
+.mr2 {margin-right:2em;}
+.mb2 {margin-bottom:2em;}
+.ml2 {margin-left:2em;}
+.pt1 {padding-top:1em;}
+.pr1 {padding-right:1em;}
+.pb1 {padding-bottom:1em;}
+.pl1 {padding-left:1em;}
+.pt2 {padding-top:2em;}
+.pr2 {padding-right:2em;}
+.pb2 {padding-bottom:2em;}
+.pl2 {padding-left:2em;}
+.red {color: red; text-align: center;}
+.bold {font-weight: bold;}
+.ctr {text-align: center;}
+input {
+ border: 1px solid #999;
+ -webkit-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
+ -moz-box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
+ box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);
+ width: 1.8em;
+}
+input[type=submit] {
+width:10em;
+background:#004F9B;
+color:#fff;
+height:30px;
+-webkit-border-radius: 15px;
+-moz-border-radius: 15px;
+border-radius: 15px;
+border: 1px solid #999;
+}
+#identform input, #checkvoteform input {
+ width:100%;;
+ display:block;
+ height: 1.8em;
+ padding-left: 0.4em;
+}
+#identform input[type=submit], #checkvoteform input[type=submit] {
+width:10em;
+margin-top: 1em;
+background:#004F9B;
+color:#fff;
+height:30px;
+-webkit-border-radius: 15px;
+-moz-border-radius: 15px;
+border-radius: 15px;
+border: 1px solid #999;
+}
+input[type=submit]:hover {
+background:#fff;
+color:#004F9B;
+}
+
+header {
+ margin-bottom: 2em;
+}
+#top-bg {
+ width: 100%;
+ position: absolute;
+ top: 0;
+ height: 12px;
+}
+header h1 {
+ margin-top: 2em;
+}
+header h1,header h4 {
+ text-align: center;
+}
+section {
+ padding: 0 1em;
+}
+#vote legend {
+ font-weight: bold;
+ text-align: center;
+}
+#checkvoteform h3, #identform h3 {
+ text-align : center;
+}
+#checkvoteform p, #identform p {
+ text-align : justify;
+}
+#identform {
+ background-color: #EEEEEE;
+ border-radius: 20px;
+ margin: 30px 0px;
+ padding: 0.5em 1em 2em;
+ box-shadow: 0px 0px 2px;
+ height: auto;
+ min-height: 15em;
+}
+#checkvoteform {
+ background-color: #EEEEEE;
+ border-radius: 20px;
+ margin: 30px 0px;
+ padding: 0.5em 1em 2em;
+ box-shadow: 0px 0px 2px;
+ height: auto;
+ min-height: 15em;
+}
+#pageexpl {
+ text-align: justify;
+ width: 90%;
+ margin: 0 auto;
+}
+.submit-like {
+width:15em;
+background:#004F9B;
+color:#fff;
+height:30px;
+-webkit-border-radius: 15px;
+-moz-border-radius: 15px;
+border-radius: 15px;
+border: 1px solid #999;
+text-decoration: none;
+padding: 0.4em;
+}
+
+.submit-like:hover {
+background:#fff;
+color:#004F9B;
+}
+#pagevote {
+ text-align: justify;
+ width: 90%;
+ margin: 0 auto;
+}
+#pagevote h3, h4, #pagevote input, #pagevote p.formvote {
+ text-align: center;
+}
+#pagevote span.candidats {
+ width: 40%;
+ display: inline-block;
+ font-size: 1.2em;
+ font-weight: bold;
+ padding: 0.5em 0;
+}
+#pagefinal {
+ text-align: justify;
+ width: 90%;
+ margin: 0 auto;
+}
+#pagemerci {
+ text-align: justify;
+ width: 90%;
+ margin: 0 auto;
+}
+#pagefinal h3, h4, #pagefinal p.formvote {
+ text-align: center;
+}
+#pagefinal input[type="submit"] {
+ margin-top: 1em;
+}
+li.parsley-required {
+ color: red;
+}
+li.parsley-mincheck {
+ color: red;
+}
+footer {
+ text-align: center;
+ margin-top: 2em;
+ border-top: 1px solid #222;
+}
+footer a {
+ color: #222 ;
+ text-decoration: none;
+}
+#pageresults {
+ text-align: center;
+ width: 90%;
+ margin: 0 auto;
+}
+.result_pres {
+ width: 12%;
+ display: inline-block;
+ font-size: 1.2em;
+ font-weight: bold;
+}
+#parsley-id-multiple-CS {
+ display:inline-block;
+}
+/* ==========================================================================
+ Helper classes
+ ========================================================================== */
+
+/*
+ * Image replacement
+ */
+
+.ir {
+ background-color: transparent;
+ border: 0;
+ overflow: hidden;
+ /* IE 6/7 fallback */
+ *text-indent: -9999px;
+}
+
+.ir:before {
+ content: "";
+ display: block;
+ width: 0;
+ height: 150%;
+}
+
+/*
+ * Hide from both screenreaders and browsers: h5bp.com/u
+ */
+
+.hidden {
+ display: none !important;
+ visibility: hidden;
+}
+
+/*
+ * Hide only visually, but have it available for screenreaders: h5bp.com/v
+ */
+
+.visuallyhidden {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+
+/*
+ * Extends the .visuallyhidden class to allow the element to be focusable
+ * when navigated to via the keyboard: h5bp.com/p
+ */
+
+.visuallyhidden.focusable:active,
+.visuallyhidden.focusable:focus {
+ clip: auto;
+ height: auto;
+ margin: 0;
+ overflow: visible;
+ position: static;
+ width: auto;
+}
+
+/*
+ * Hide visually and from screenreaders, but maintain layout
+ */
+
+.invisible {
+ visibility: hidden;
+}
+
+/*
+ * Clearfix: contain floats
+ *
+ * For modern browsers
+ * 1. The space content is one way to avoid an Opera bug when the
+ * `contenteditable` attribute is included anywhere else in the document.
+ * Otherwise it causes space to appear at the top and bottom of elements
+ * that receive the `clearfix` class.
+ * 2. The use of `table` rather than `block` is only necessary if using
+ * `:before` to contain the top-margins of child elements.
+ */
+
+.clearfix:before,
+.clearfix:after {
+ content: " "; /* 1 */
+ display: table; /* 2 */
+}
+
+.clearfix:after {
+ clear: both;
+}
+
+/*
+ * For IE 6/7 only
+ * Include this rule to trigger hasLayout and contain floats.
+ */
+
+.clearfix {
+ *zoom: 1;
+}
+
+/* ==========================================================================
+ EXAMPLE Media Queries for Responsive Design.
+ These examples override the primary ('mobile first') styles.
+ Modify as content requires.
+ ========================================================================== */
+
+@media only screen and (min-width: 832px) {
+ #identform {
+ width: 40%;
+ vertical-align: top;
+ display: inline-block;
+ margin: 0 4%;
+ }
+ #checkvoteform {
+ width: 40%;
+ vertical-align: top;
+ display: inline-block;
+ margin: 0 2%;
+ }
+ #identform input, #checkvoteform input {
+ width: 70%
+ }
+ #pageexpl {
+ width: 50%;
+ }
+ #pagevote {
+ width: 50%;
+ }
+ #pagefinal {
+ width: 50%;
+ }
+ #pagemerci {
+ width: 50%;
+ }
+}
+
+@media print,
+ (-o-min-device-pixel-ratio: 5/4),
+ (-webkit-min-device-pixel-ratio: 1.25),
+ (min-resolution: 120dpi) {
+ /* Style adjustments for high resolution devices */
+}
+
+/* ==========================================================================
+ Print styles.
+ Inlined to avoid required HTTP connection: h5bp.com/r
+ ========================================================================== */
+
+@media print {
+ * {
+ background: transparent !important;
+ color: #000 !important; /* Black prints faster: h5bp.com/s */
+ box-shadow: none !important;
+ text-shadow: none !important;
+ }
+
+ a,
+ a:visited {
+ text-decoration: underline;
+ }
+
+ a[href]:after {
+ content: " (" attr(href) ")";
+ }
+
+ abbr[title]:after {
+ content: " (" attr(title) ")";
+ }
+
+ /*
+ * Don't show links for images, or javascript/internal links
+ */
+
+ .ir a:after,
+ a[href^="javascript:"]:after,
+ a[href^="#"]:after {
+ content: "";
+ }
+
+ pre,
+ blockquote {
+ border: 1px solid #999;
+ page-break-inside: avoid;
+ }
+
+ thead {
+ display: table-header-group; /* h5bp.com/t */
+ }
+
+ tr,
+ img {
+ page-break-inside: avoid;
+ }
+
+ img {
+ max-width: 100% !important;
+ }
+
+ @page {
+ margin: 0.5cm;
+ }
+
+ p,
+ h2,
+ h3 {
+ orphans: 3;
+ widows: 3;
+ }
+
+ h2,
+ h3 {
+ page-break-after: avoid;
+ }
+}
diff --git a/css/normalize.css b/css/normalize.css
new file mode 100644
index 0000000..144ebbf
--- /dev/null
+++ b/css/normalize.css
@@ -0,0 +1,527 @@
+/*! normalize.css v1.1.3 | MIT License | git.io/normalize */
+
+/* ==========================================================================
+ HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
+ */
+
+audio,
+canvas,
+video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
+ * Known issue: no IE 6 support.
+ */
+
+[hidden] {
+ display: none;
+}
+
+/* ==========================================================================
+ Base
+ ========================================================================== */
+
+/**
+ * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
+ * `em` units.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-size: 100%; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Address `font-family` inconsistency between `textarea` and other form
+ * elements.
+ */
+
+html,
+button,
+input,
+select,
+textarea {
+ font-family: sans-serif;
+}
+
+/**
+ * Address margins handled incorrectly in IE 6/7.
+ */
+
+body {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Links
+ ========================================================================== */
+
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+
+a:focus {
+ outline: thin dotted;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* ==========================================================================
+ Typography
+ ========================================================================== */
+
+/**
+ * Address font sizes and margins set differently in IE 6/7.
+ * Address font sizes within `section` and `article` in Firefox 4+, Safari 5,
+ * and Chrome.
+ */
+
+h1 {
+ font-size: 1.6em;
+ margin: 0.67em 0;
+}
+
+h2 {
+ font-size: 1.4em;
+ margin: 0.83em 0;
+}
+
+h3 {
+ font-size: 1.17em;
+ margin: 1em 0;
+}
+
+h4 {
+ font-size: 1em;
+ margin: 1.33em 0;
+}
+
+h5 {
+ font-size: 0.83em;
+ margin: 1.67em 0;
+}
+
+h6 {
+ font-size: 0.67em;
+ margin: 2.33em 0;
+}
+
+/**
+ * Address styling not present in IE 7/8/9, Safari 5, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+blockquote {
+ margin: 1em 40px;
+}
+
+/**
+ * Address styling not present in Safari 5 and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ * Known issue: no IE 6/7 normalization.
+ */
+
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Address styling not present in IE 6/7/8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Address margins set differently in IE 6/7.
+ */
+
+p,
+pre {
+ margin: 1em 0;
+}
+
+/**
+ * Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, serif;
+ _font-family: 'courier new', monospace;
+ font-size: 1em;
+}
+
+/**
+ * Improve readability of pre-formatted text in all browsers.
+ */
+
+pre {
+ white-space: pre;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+/**
+ * Address CSS quotes not supported in IE 6/7.
+ */
+
+q {
+ quotes: none;
+}
+
+/**
+ * Address `quotes` property not supported in Safari 4.
+ */
+
+q:before,
+q:after {
+ content: '';
+ content: none;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* ==========================================================================
+ Lists
+ ========================================================================== */
+
+/**
+ * Address margins set differently in IE 6/7.
+ */
+
+dl,
+menu,
+ol,
+ul {
+ margin: 1em 0;
+}
+
+dd {
+ margin: 0 0 0 40px;
+}
+
+/**
+ * Address paddings set differently in IE 6/7.
+ */
+
+menu,
+ol,
+ul {
+ padding: 0 0 0 40px;
+}
+
+/**
+ * Correct list images handled incorrectly in IE 7.
+ */
+
+nav ul,
+nav ol {
+ list-style: none;
+ list-style-image: none;
+}
+
+/* ==========================================================================
+ Embedded content
+ ========================================================================== */
+
+/**
+ * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
+ * 2. Improve image quality when scaled in IE 7.
+ */
+
+img {
+ border: 0; /* 1 */
+ -ms-interpolation-mode: bicubic; /* 2 */
+}
+
+/**
+ * Correct overflow displayed oddly in IE 9.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* ==========================================================================
+ Figures
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
+ */
+
+figure {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Forms
+ ========================================================================== */
+
+/**
+ * Correct margin displayed oddly in IE 6/7.
+ */
+
+form {
+ margin: 0;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct color not being inherited in IE 6/7/8/9.
+ * 2. Correct text not wrapping in Firefox 3.
+ * 3. Correct alignment displayed oddly in IE 6/7.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0;
+ white-space: normal; /* 2 */
+ *margin-left: -7px; /* 3 */
+}
+
+/**
+ * 1. Correct font size not being inherited in all browsers.
+ * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
+ * and Chrome.
+ * 3. Improve appearance and consistency in all browsers.
+ */
+
+button,
+input,
+select,
+textarea {
+ font-size: 100%; /* 1 */
+ margin: 0; /* 2 */
+ vertical-align: baseline; /* 3 */
+ *vertical-align: middle; /* 3 */
+}
+
+/**
+ * Address Firefox 3+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+ line-height: normal;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
+ * Known issue: inner spacing remains in IE 6.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+ *overflow: visible; /* 4 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * 1. Address box sizing set to content-box in IE 8/9.
+ * 2. Remove excess padding in IE 8/9.
+ * 3. Remove excess padding in IE 7.
+ * Known issue: excess padding remains in IE 6.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+ *height: 13px; /* 3 */
+ *width: 13px; /* 3 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
+ * (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box; /* 2 */
+ box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Remove inner padding and border in Firefox 3+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * 1. Remove default vertical scrollbar in IE 6/7/8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+
+textarea {
+ overflow: auto; /* 1 */
+ vertical-align: top; /* 2 */
+}
+
+/* ==========================================================================
+ Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
diff --git a/explication.php b/explication.php
new file mode 100644
index 0000000..f6dd2bf
--- /dev/null
+++ b/explication.php
@@ -0,0 +1,85 @@
+prepare("SELECT nom,prenom,email FROM clients WHERE reference = :reference");
+ $stmt->execute(array ('reference' =>$reference));
+
+ if ($stmt->rowCount() !== 0) {
+ while ($ligne = $stmt->fetch(PDO::FETCH_OBJ)) {
+ // On déclare les variables de session
+ $_SESSION['prenom'] = $ligne->prenom;
+ $_SESSION['nom'] = $ligne->nom;
+ $_SESSION['email'] = $ligne->email;
+ }
+ } else header('Location: index.php?$unknow=true');
+}
+
+// Le coopérateur à déjà voté ?
+$sql = 'SELECT vote FROM clients WHERE reference = :reference';
+$stmt = $bdd->prepare($sql);
+$stmt->execute(array ('reference' => $_SESSION['reference']));
+while ($results = $stmt->fetch(PDO::FETCH_OBJ)) {
+ // Si déjà voté, on redirige vers index.php avec un message d'avertissement
+ if ($results->vote == 1) {
+ header('Location: index.php?$voted=true');
+ // On vide les variables de session
+ $_SESSION = array();
+ // On détruit la session
+ session_destroy();
+ }
+}
+
+// On va chercher l'url des documents relatifs à l'AG
+$sql = "SELECT option_name, option_value FROM admin WHERE option_name = :option";
+$stmt = $bdd->prepare($sql);
+$option = 'url_docs';
+$stmt->bindParam(':option', $option, PDO::PARAM_STR);
+$stmt->execute();
+$results = $stmt->fetch();
+$url_docs = $results['option_value'];
+$stmt = null;
+?>
+
+
+
+
+
+
+
+
+
+
Bonjour , et bienvenue sur la plateforme de vote en ligne d'Ouvaton !
+
+ Vous allez pouvoir voter grâce au formulaire de vote en ligne.
+ Ce formulaire comprend les résolutions pour cette Assemblée Générale Ordinaire, ainsi que le vote pour l'élection des membres du Conseil de Surveillance.
+
+
+
+
+ Vous pouvez consulter les documents relatifs à l’Assemblée Générale 2019 sur le site de la coopérative, à l'adresse
+ .
+ Vous y trouverez les différents rapports (du Directoire, du Conseil de Surveillance), les comptes et la présentation des candidats au Conseil de Surveillance.
+
+
+ Pour garantir la sécurité et la confidentialité de votre vote, ce site utilise HTTPS pour chiffrer la connexion, et un système de vote qui ne permet pas de faire un lien entre les votes enregistrés et les coopérateurs inscrits.
+ Une clé unique est générée lors de votre vote. Cette clé n'est connue que de vous seul-e, et elle vous permet de vérifier à tout moment que les données de votre vote sont bien enregistrées et ne sont pas modifiées.
+
';
+ }
+ }
+
+ /**
+ * POP3 connection error handler.
+ * @param integer $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param integer $errline
+ * @access private
+ */
+ private function catchWarning($errno, $errstr, $errfile, $errline)
+ {
+ $this->setError(array(
+ 'error' => "Connecting to the POP3 server raised a PHP warning: ",
+ 'errno' => $errno,
+ 'errstr' => $errstr,
+ 'errfile' => $errfile,
+ 'errline' => $errline
+ ));
+ }
+}
diff --git a/inc/PHPMailer/class.smtp.php b/inc/PHPMailer/class.smtp.php
new file mode 100644
index 0000000..d699197
--- /dev/null
+++ b/inc/PHPMailer/class.smtp.php
@@ -0,0 +1,1132 @@
+
+ * @author Jim Jagielski (jimjag)
+ * @author Andy Prevost (codeworxtech)
+ * @author Brent R. Matzelle (original founder)
+ * @copyright 2014 Marcus Bointon
+ * @copyright 2010 - 2012 Jim Jagielski
+ * @copyright 2004 - 2009 Andy Prevost
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @note This program is distributed in the hope that it will be useful - WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+/**
+ * PHPMailer RFC821 SMTP email transport class.
+ * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
+ * @package PHPMailer
+ * @author Chris Ryan
+ * @author Marcus Bointon
+ */
+class SMTP
+{
+ /**
+ * The PHPMailer SMTP version number.
+ * @type string
+ */
+ const VERSION = '5.2.9';
+
+ /**
+ * SMTP line break constant.
+ * @type string
+ */
+ const CRLF = "\r\n";
+
+ /**
+ * The SMTP port to use if one is not specified.
+ * @type integer
+ */
+ const DEFAULT_SMTP_PORT = 25;
+
+ /**
+ * The maximum line length allowed by RFC 2822 section 2.1.1
+ * @type integer
+ */
+ const MAX_LINE_LENGTH = 998;
+
+ /**
+ * Debug level for no output
+ */
+ const DEBUG_OFF = 0;
+
+ /**
+ * Debug level to show client -> server messages
+ */
+ const DEBUG_CLIENT = 1;
+
+ /**
+ * Debug level to show client -> server and server -> client messages
+ */
+ const DEBUG_SERVER = 2;
+
+ /**
+ * Debug level to show connection status, client -> server and server -> client messages
+ */
+ const DEBUG_CONNECTION = 3;
+
+ /**
+ * Debug level to show all messages
+ */
+ const DEBUG_LOWLEVEL = 4;
+
+ /**
+ * The PHPMailer SMTP Version number.
+ * @type string
+ * @deprecated Use the `VERSION` constant instead
+ * @see SMTP::VERSION
+ */
+ public $Version = '5.2.9';
+
+ /**
+ * SMTP server port number.
+ * @type integer
+ * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead
+ * @see SMTP::DEFAULT_SMTP_PORT
+ */
+ public $SMTP_PORT = 25;
+
+ /**
+ * SMTP reply line ending.
+ * @type string
+ * @deprecated Use the `CRLF` constant instead
+ * @see SMTP::CRLF
+ */
+ public $CRLF = "\r\n";
+
+ /**
+ * Debug output level.
+ * Options:
+ * * self::DEBUG_OFF (`0`) No debug output, default
+ * * self::DEBUG_CLIENT (`1`) Client commands
+ * * self::DEBUG_SERVER (`2`) Client commands and server responses
+ * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
+ * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages
+ * @type integer
+ */
+ public $do_debug = self::DEBUG_OFF;
+
+ /**
+ * How to handle debug output.
+ * Options:
+ * * `echo` Output plain-text as-is, appropriate for CLI
+ * * `html` Output escaped, line breaks converted to ` `, appropriate for browser output
+ * * `error_log` Output to error log as configured in php.ini
+ *
+ * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
+ *
+ * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
+ *
+ * @type string|callable
+ */
+ public $Debugoutput = 'echo';
+
+ /**
+ * Whether to use VERP.
+ * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path
+ * @link http://www.postfix.org/VERP_README.html Info on VERP
+ * @type boolean
+ */
+ public $do_verp = false;
+
+ /**
+ * The timeout value for connection, in seconds.
+ * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
+ * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
+ * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2
+ * @type integer
+ */
+ public $Timeout = 300;
+
+ /**
+ * How long to wait for commands to complete, in seconds.
+ * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
+ * @type integer
+ */
+ public $Timelimit = 300;
+
+ /**
+ * The socket for the server connection.
+ * @type resource
+ */
+ protected $smtp_conn;
+
+ /**
+ * Error message, if any, for the last call.
+ * @type array
+ */
+ protected $error = array();
+
+ /**
+ * The reply the server sent to us for HELO.
+ * If null, no HELO string has yet been received.
+ * @type string|null
+ */
+ protected $helo_rply = null;
+
+ /**
+ * The set of SMTP extensions sent in reply to EHLO command.
+ * Indexes of the array are extension names.
+ * Value at index 'HELO' or 'EHLO' (according to command that was sent)
+ * represents the server name. In case of HELO it is the only element of the array.
+ * Other values can be boolean TRUE or an array containing extension options.
+ * If null, no HELO/EHLO string has yet been received.
+ * @type array|null
+ */
+ protected $server_caps = null;
+
+ /**
+ * The most recent reply received from the server.
+ * @type string
+ */
+ protected $last_reply = '';
+
+ /**
+ * Output debugging info via a user-selected method.
+ * @see SMTP::$Debugoutput
+ * @see SMTP::$do_debug
+ * @param string $str Debug string to output
+ * @param integer $level The debug level of this message; see DEBUG_* constants
+ * @return void
+ */
+ protected function edebug($str, $level = 0)
+ {
+ if ($level > $this->do_debug) {
+ return;
+ }
+ //Avoid clash with built-in function names
+ if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
+ call_user_func($this->Debugoutput, $str, $this->do_debug);
+ return;
+ }
+ switch ($this->Debugoutput) {
+ case 'error_log':
+ //Don't output, just log
+ error_log($str);
+ break;
+ case 'html':
+ //Cleans up output a bit for a better looking, HTML-safe output
+ echo htmlentities(
+ preg_replace('/[\r\n]+/', '', $str),
+ ENT_QUOTES,
+ 'UTF-8'
+ )
+ . " \n";
+ break;
+ case 'echo':
+ default:
+ //Normalize line breaks
+ $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
+ echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
+ "\n",
+ "\n \t ",
+ trim($str)
+ )."\n";
+ }
+ }
+
+ /**
+ * Connect to an SMTP server.
+ * @param string $host SMTP server IP or host name
+ * @param integer $port The port number to connect to
+ * @param integer $timeout How long to wait for the connection to open
+ * @param array $options An array of options for stream_context_create()
+ * @access public
+ * @return boolean
+ */
+ public function connect($host, $port = null, $timeout = 30, $options = array())
+ {
+ static $streamok;
+ //This is enabled by default since 5.0.0 but some providers disable it
+ //Check this once and cache the result
+ if (is_null($streamok)) {
+ $streamok = function_exists('stream_socket_client');
+ }
+ // Clear errors to avoid confusion
+ $this->error = array();
+ // Make sure we are __not__ connected
+ if ($this->connected()) {
+ // Already connected, generate error
+ $this->error = array('error' => 'Already connected to a server');
+ return false;
+ }
+ if (empty($port)) {
+ $port = self::DEFAULT_SMTP_PORT;
+ }
+ // Connect to the SMTP server
+ $this->edebug(
+ "Connection: opening to $host:$port, t=$timeout, opt=".var_export($options, true),
+ self::DEBUG_CONNECTION
+ );
+ $errno = 0;
+ $errstr = '';
+ if ($streamok) {
+ $socket_context = stream_context_create($options);
+ //Suppress errors; connection failures are handled at a higher level
+ $this->smtp_conn = @stream_socket_client(
+ $host . ":" . $port,
+ $errno,
+ $errstr,
+ $timeout,
+ STREAM_CLIENT_CONNECT,
+ $socket_context
+ );
+ } else {
+ //Fall back to fsockopen which should work in more places, but is missing some features
+ $this->edebug(
+ "Connection: stream_socket_client not available, falling back to fsockopen",
+ self::DEBUG_CONNECTION
+ );
+ $this->smtp_conn = fsockopen(
+ $host,
+ $port,
+ $errno,
+ $errstr,
+ $timeout
+ );
+ }
+ // Verify we connected properly
+ if (!is_resource($this->smtp_conn)) {
+ $this->error = array(
+ 'error' => 'Failed to connect to server',
+ 'errno' => $errno,
+ 'errstr' => $errstr
+ );
+ $this->edebug(
+ 'SMTP ERROR: ' . $this->error['error']
+ . ": $errstr ($errno)",
+ self::DEBUG_CLIENT
+ );
+ return false;
+ }
+ $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
+ // SMTP server can take longer to respond, give longer timeout for first read
+ // Windows does not have support for this timeout function
+ if (substr(PHP_OS, 0, 3) != 'WIN') {
+ $max = ini_get('max_execution_time');
+ if ($max != 0 && $timeout > $max) { // Don't bother if unlimited
+ @set_time_limit($timeout);
+ }
+ stream_set_timeout($this->smtp_conn, $timeout, 0);
+ }
+ // Get any announcement
+ $announce = $this->get_lines();
+ $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
+ return true;
+ }
+
+ /**
+ * Initiate a TLS (encrypted) session.
+ * @access public
+ * @return boolean
+ */
+ public function startTLS()
+ {
+ if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
+ return false;
+ }
+ // Begin encrypted connection
+ if (!stream_socket_enable_crypto(
+ $this->smtp_conn,
+ true,
+ STREAM_CRYPTO_METHOD_TLS_CLIENT
+ )) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Perform SMTP authentication.
+ * Must be run after hello().
+ * @see hello()
+ * @param string $username The user name
+ * @param string $password The password
+ * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5)
+ * @param string $realm The auth realm for NTLM
+ * @param string $workstation The auth workstation for NTLM
+ * @access public
+ * @return boolean True if successfully authenticated.
+ */
+ public function authenticate(
+ $username,
+ $password,
+ $authtype = null,
+ $realm = '',
+ $workstation = ''
+ ) {
+ if (!$this->server_caps) {
+ $this->error = array('error' => 'Authentication is not allowed before HELO/EHLO');
+ return false;
+ }
+
+ if (array_key_exists('EHLO', $this->server_caps)) {
+ // SMTP extensions are available. Let's try to find a proper authentication method
+
+ if (!array_key_exists('AUTH', $this->server_caps)) {
+ $this->error = array( 'error' => 'Authentication is not allowed at this stage' );
+ // 'at this stage' means that auth may be allowed after the stage changes
+ // e.g. after STARTTLS
+ return false;
+ }
+
+ self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
+ self::edebug(
+ 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
+ self::DEBUG_LOWLEVEL
+ );
+
+ if (empty($authtype)) {
+ foreach (array('LOGIN', 'CRAM-MD5', 'NTLM', 'PLAIN') as $method) {
+ if (in_array($method, $this->server_caps['AUTH'])) {
+ $authtype = $method;
+ break;
+ }
+ }
+ if (empty($authtype)) {
+ $this->error = array( 'error' => 'No supported authentication methods found' );
+ return false;
+ }
+ self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL);
+ }
+
+ if (!in_array($authtype, $this->server_caps['AUTH'])) {
+ $this->error = array( 'error' => 'The requested authentication method "'
+ . $authtype . '" is not supported by the server' );
+ return false;
+ }
+ } elseif (empty($authtype)) {
+ $authtype = 'LOGIN';
+ }
+ switch ($authtype) {
+ case 'PLAIN':
+ // Start authentication
+ if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
+ return false;
+ }
+ // Send encoded username and password
+ if (!$this->sendCommand(
+ 'User & Password',
+ base64_encode("\0" . $username . "\0" . $password),
+ 235
+ )
+ ) {
+ return false;
+ }
+ break;
+ case 'LOGIN':
+ // Start authentication
+ if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
+ return false;
+ }
+ if (!$this->sendCommand("Username", base64_encode($username), 334)) {
+ return false;
+ }
+ if (!$this->sendCommand("Password", base64_encode($password), 235)) {
+ return false;
+ }
+ break;
+ case 'NTLM':
+ /*
+ * ntlm_sasl_client.php
+ * Bundled with Permission
+ *
+ * How to telnet in windows:
+ * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
+ * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
+ */
+ require_once 'extras/ntlm_sasl_client.php';
+ $temp = new stdClass();
+ $ntlm_client = new ntlm_sasl_client_class;
+ //Check that functions are available
+ if (!$ntlm_client->Initialize($temp)) {
+ $this->error = array('error' => $temp->error);
+ $this->edebug(
+ 'You need to enable some modules in your php.ini file: '
+ . $this->error['error'],
+ self::DEBUG_CLIENT
+ );
+ return false;
+ }
+ //msg1
+ $msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1
+
+ if (!$this->sendCommand(
+ 'AUTH NTLM',
+ 'AUTH NTLM ' . base64_encode($msg1),
+ 334
+ )
+ ) {
+ return false;
+ }
+ //Though 0 based, there is a white space after the 3 digit number
+ //msg2
+ $challenge = substr($this->last_reply, 3);
+ $challenge = base64_decode($challenge);
+ $ntlm_res = $ntlm_client->NTLMResponse(
+ substr($challenge, 24, 8),
+ $password
+ );
+ //msg3
+ $msg3 = $ntlm_client->TypeMsg3(
+ $ntlm_res,
+ $username,
+ $realm,
+ $workstation
+ );
+ // send encoded username
+ return $this->sendCommand('Username', base64_encode($msg3), 235);
+ case 'CRAM-MD5':
+ // Start authentication
+ if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
+ return false;
+ }
+ // Get the challenge
+ $challenge = base64_decode(substr($this->last_reply, 4));
+
+ // Build the response
+ $response = $username . ' ' . $this->hmac($challenge, $password);
+
+ // send encoded credentials
+ return $this->sendCommand('Username', base64_encode($response), 235);
+ default:
+ $this->error = array( 'error' => 'Authentication method "' . $authtype . '" is not supported' );
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Calculate an MD5 HMAC hash.
+ * Works like hash_hmac('md5', $data, $key)
+ * in case that function is not available
+ * @param string $data The data to hash
+ * @param string $key The key to hash with
+ * @access protected
+ * @return string
+ */
+ protected function hmac($data, $key)
+ {
+ if (function_exists('hash_hmac')) {
+ return hash_hmac('md5', $data, $key);
+ }
+
+ // The following borrowed from
+ // http://php.net/manual/en/function.mhash.php#27225
+
+ // RFC 2104 HMAC implementation for php.
+ // Creates an md5 HMAC.
+ // Eliminates the need to install mhash to compute a HMAC
+ // by Lance Rushing
+
+ $bytelen = 64; // byte length for md5
+ if (strlen($key) > $bytelen) {
+ $key = pack('H*', md5($key));
+ }
+ $key = str_pad($key, $bytelen, chr(0x00));
+ $ipad = str_pad('', $bytelen, chr(0x36));
+ $opad = str_pad('', $bytelen, chr(0x5c));
+ $k_ipad = $key ^ $ipad;
+ $k_opad = $key ^ $opad;
+
+ return md5($k_opad . pack('H*', md5($k_ipad . $data)));
+ }
+
+ /**
+ * Check connection state.
+ * @access public
+ * @return boolean True if connected.
+ */
+ public function connected()
+ {
+ if (is_resource($this->smtp_conn)) {
+ $sock_status = stream_get_meta_data($this->smtp_conn);
+ if ($sock_status['eof']) {
+ // The socket is valid but we are not connected
+ $this->edebug(
+ 'SMTP NOTICE: EOF caught while checking if connected',
+ self::DEBUG_CLIENT
+ );
+ $this->close();
+ return false;
+ }
+ return true; // everything looks good
+ }
+ return false;
+ }
+
+ /**
+ * Close the socket and clean up the state of the class.
+ * Don't use this function without first trying to use QUIT.
+ * @see quit()
+ * @access public
+ * @return void
+ */
+ public function close()
+ {
+ $this->error = array();
+ $this->server_caps = null;
+ $this->helo_rply = null;
+ if (is_resource($this->smtp_conn)) {
+ // close the connection and cleanup
+ fclose($this->smtp_conn);
+ $this->smtp_conn = null; //Makes for cleaner serialization
+ $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
+ }
+ }
+
+ /**
+ * Send an SMTP DATA command.
+ * Issues a data command and sends the msg_data to the server,
+ * finializing the mail transaction. $msg_data is the message
+ * that is to be send with the headers. Each header needs to be
+ * on a single line followed by a with the message headers
+ * and the message body being separated by and additional .
+ * Implements rfc 821: DATA
+ * @param string $msg_data Message data to send
+ * @access public
+ * @return boolean
+ */
+ public function data($msg_data)
+ {
+ //This will use the standard timelimit
+ if (!$this->sendCommand('DATA', 'DATA', 354)) {
+ return false;
+ }
+
+ /* The server is ready to accept data!
+ * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF)
+ * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
+ * smaller lines to fit within the limit.
+ * We will also look for lines that start with a '.' and prepend an additional '.'.
+ * NOTE: this does not count towards line-length limit.
+ */
+
+ // Normalize line breaks before exploding
+ $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data));
+
+ /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
+ * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
+ * process all lines before a blank line as headers.
+ */
+
+ $field = substr($lines[0], 0, strpos($lines[0], ':'));
+ $in_headers = false;
+ if (!empty($field) && strpos($field, ' ') === false) {
+ $in_headers = true;
+ }
+
+ foreach ($lines as $line) {
+ $lines_out = array();
+ if ($in_headers and $line == '') {
+ $in_headers = false;
+ }
+ //We need to break this line up into several smaller lines
+ //This is a small micro-optimisation: isset($str[$len]) is equivalent to (strlen($str) > $len)
+ while (isset($line[self::MAX_LINE_LENGTH])) {
+ //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
+ //so as to avoid breaking in the middle of a word
+ $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
+ if (!$pos) { //Deliberately matches both false and 0
+ //No nice break found, add a hard break
+ $pos = self::MAX_LINE_LENGTH - 1;
+ $lines_out[] = substr($line, 0, $pos);
+ $line = substr($line, $pos);
+ } else {
+ //Break at the found point
+ $lines_out[] = substr($line, 0, $pos);
+ //Move along by the amount we dealt with
+ $line = substr($line, $pos + 1);
+ }
+ //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
+ if ($in_headers) {
+ $line = "\t" . $line;
+ }
+ }
+ $lines_out[] = $line;
+
+ //Send the lines to the server
+ foreach ($lines_out as $line_out) {
+ //RFC2821 section 4.5.2
+ if (!empty($line_out) and $line_out[0] == '.') {
+ $line_out = '.' . $line_out;
+ }
+ $this->client_send($line_out . self::CRLF);
+ }
+ }
+
+ //Message data has been sent, complete the command
+ //Increase timelimit for end of DATA command
+ $savetimelimit = $this->Timelimit;
+ $this->Timelimit = $this->Timelimit * 2;
+ $result = $this->sendCommand('DATA END', '.', 250);
+ //Restore timelimit
+ $this->Timelimit = $savetimelimit;
+ return $result;
+ }
+
+ /**
+ * Send an SMTP HELO or EHLO command.
+ * Used to identify the sending server to the receiving server.
+ * This makes sure that client and server are in a known state.
+ * Implements RFC 821: HELO
+ * and RFC 2821 EHLO.
+ * @param string $host The host name or IP to connect to
+ * @access public
+ * @return boolean
+ */
+ public function hello($host = '')
+ {
+ //Try extended hello first (RFC 2821)
+ return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
+ }
+
+ /**
+ * Send an SMTP HELO or EHLO command.
+ * Low-level implementation used by hello()
+ * @see hello()
+ * @param string $hello The HELO string
+ * @param string $host The hostname to say we are
+ * @access protected
+ * @return boolean
+ */
+ protected function sendHello($hello, $host)
+ {
+ $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
+ $this->helo_rply = $this->last_reply;
+ if ($noerror) {
+ $this->parseHelloFields($hello);
+ } else {
+ $this->server_caps = null;
+ }
+ return $noerror;
+ }
+
+ /**
+ * Parse a reply to HELO/EHLO command to discover server extensions.
+ * In case of HELO, the only parameter that can be discovered is a server name.
+ * @access protected
+ * @param string $type - 'HELO' or 'EHLO'
+ */
+ protected function parseHelloFields($type)
+ {
+ $this->server_caps = array();
+ $lines = explode("\n", $this->last_reply);
+ foreach ($lines as $n => $s) {
+ $s = trim(substr($s, 4));
+ if (!$s) {
+ continue;
+ }
+ $fields = explode(' ', $s);
+ if ($fields) {
+ if (!$n) {
+ $name = $type;
+ $fields = $fields[0];
+ } else {
+ $name = array_shift($fields);
+ if ($name == 'SIZE') {
+ $fields = ($fields) ? $fields[0] : 0;
+ }
+ }
+ $this->server_caps[$name] = ($fields ? $fields : true);
+ }
+ }
+ }
+
+ /**
+ * Send an SMTP MAIL command.
+ * Starts a mail transaction from the email address specified in
+ * $from. Returns true if successful or false otherwise. If True
+ * the mail transaction is started and then one or more recipient
+ * commands may be called followed by a data command.
+ * Implements rfc 821: MAIL FROM:
+ * @param string $from Source address of this message
+ * @access public
+ * @return boolean
+ */
+ public function mail($from)
+ {
+ $useVerp = ($this->do_verp ? ' XVERP' : '');
+ return $this->sendCommand(
+ 'MAIL FROM',
+ 'MAIL FROM:<' . $from . '>' . $useVerp,
+ 250
+ );
+ }
+
+ /**
+ * Send an SMTP QUIT command.
+ * Closes the socket if there is no error or the $close_on_error argument is true.
+ * Implements from rfc 821: QUIT
+ * @param boolean $close_on_error Should the connection close if an error occurs?
+ * @access public
+ * @return boolean
+ */
+ public function quit($close_on_error = true)
+ {
+ $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
+ $err = $this->error; //Save any error
+ if ($noerror or $close_on_error) {
+ $this->close();
+ $this->error = $err; //Restore any error from the quit command
+ }
+ return $noerror;
+ }
+
+ /**
+ * Send an SMTP RCPT command.
+ * Sets the TO argument to $toaddr.
+ * Returns true if the recipient was accepted false if it was rejected.
+ * Implements from rfc 821: RCPT TO:
+ * @param string $toaddr The address the message is being sent to
+ * @access public
+ * @return boolean
+ */
+ public function recipient($toaddr)
+ {
+ return $this->sendCommand(
+ 'RCPT TO',
+ 'RCPT TO:<' . $toaddr . '>',
+ array(250, 251)
+ );
+ }
+
+ /**
+ * Send an SMTP RSET command.
+ * Abort any transaction that is currently in progress.
+ * Implements rfc 821: RSET
+ * @access public
+ * @return boolean True on success.
+ */
+ public function reset()
+ {
+ return $this->sendCommand('RSET', 'RSET', 250);
+ }
+
+ /**
+ * Send a command to an SMTP server and check its return code.
+ * @param string $command The command name - not sent to the server
+ * @param string $commandstring The actual command to send
+ * @param integer|array $expect One or more expected integer success codes
+ * @access protected
+ * @return boolean True on success.
+ */
+ protected function sendCommand($command, $commandstring, $expect)
+ {
+ if (!$this->connected()) {
+ $this->error = array(
+ 'error' => "Called $command without being connected"
+ );
+ return false;
+ }
+ $this->client_send($commandstring . self::CRLF);
+
+ $this->last_reply = $this->get_lines();
+ // Fetch SMTP code and possible error code explanation
+ $matches = array();
+ if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
+ $code = $matches[1];
+ $code_ex = (count($matches) > 2 ? $matches[2] : null);
+ // Cut off error code from each response line
+ $detail = preg_replace(
+ "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m",
+ '',
+ $this->last_reply
+ );
+ } else { // Fall back to simple parsing if regex fails
+ $code = substr($this->last_reply, 0, 3);
+ $code_ex = null;
+ $detail = substr($this->last_reply, 4);
+ }
+
+ $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
+
+ if (!in_array($code, (array)$expect)) {
+ $this->error = array(
+ 'error' => "$command command failed",
+ 'smtp_code' => $code,
+ 'smtp_code_ex' => $code_ex,
+ 'detail' => $detail
+ );
+ $this->edebug(
+ 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
+ self::DEBUG_CLIENT
+ );
+ return false;
+ }
+
+ $this->error = array();
+ return true;
+ }
+
+ /**
+ * Send an SMTP SAML command.
+ * Starts a mail transaction from the email address specified in $from.
+ * Returns true if successful or false otherwise. If True
+ * the mail transaction is started and then one or more recipient
+ * commands may be called followed by a data command. This command
+ * will send the message to the users terminal if they are logged
+ * in and send them an email.
+ * Implements rfc 821: SAML FROM:
+ * @param string $from The address the message is from
+ * @access public
+ * @return boolean
+ */
+ public function sendAndMail($from)
+ {
+ return $this->sendCommand('SAML', "SAML FROM:$from", 250);
+ }
+
+ /**
+ * Send an SMTP VRFY command.
+ * @param string $name The name to verify
+ * @access public
+ * @return boolean
+ */
+ public function verify($name)
+ {
+ return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
+ }
+
+ /**
+ * Send an SMTP NOOP command.
+ * Used to keep keep-alives alive, doesn't actually do anything
+ * @access public
+ * @return boolean
+ */
+ public function noop()
+ {
+ return $this->sendCommand('NOOP', 'NOOP', 250);
+ }
+
+ /**
+ * Send an SMTP TURN command.
+ * This is an optional command for SMTP that this class does not support.
+ * This method is here to make the RFC821 Definition complete for this class
+ * and _may_ be implemented in future
+ * Implements from rfc 821: TURN
+ * @access public
+ * @return boolean
+ */
+ public function turn()
+ {
+ $this->error = array(
+ 'error' => 'The SMTP TURN command is not implemented'
+ );
+ $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
+ return false;
+ }
+
+ /**
+ * Send raw data to the server.
+ * @param string $data The data to send
+ * @access public
+ * @return integer|boolean The number of bytes sent to the server or false on error
+ */
+ public function client_send($data)
+ {
+ $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
+ return fwrite($this->smtp_conn, $data);
+ }
+
+ /**
+ * Get the latest error.
+ * @access public
+ * @return array
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * Get SMTP extensions available on the server
+ * @access public
+ * @return array|null
+ */
+ public function getServerExtList()
+ {
+ return $this->server_caps;
+ }
+
+ /**
+ * A multipurpose method
+ * The method works in three ways, dependent on argument value and current state
+ * 1. HELO/EHLO was not sent - returns null and set up $this->error
+ * 2. HELO was sent
+ * $name = 'HELO': returns server name
+ * $name = 'EHLO': returns boolean false
+ * $name = any string: returns null and set up $this->error
+ * 3. EHLO was sent
+ * $name = 'HELO'|'EHLO': returns server name
+ * $name = any string: if extension $name exists, returns boolean True
+ * or its options. Otherwise returns boolean False
+ * In other words, one can use this method to detect 3 conditions:
+ * - null returned: handshake was not or we don't know about ext (refer to $this->error)
+ * - false returned: the requested feature exactly not exists
+ * - positive value returned: the requested feature exists
+ * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
+ * @return mixed
+ */
+ public function getServerExt($name)
+ {
+ if (!$this->server_caps) {
+ $this->error = array('No HELO/EHLO was sent');
+ return null;
+ }
+
+ // the tight logic knot ;)
+ if (!array_key_exists($name, $this->server_caps)) {
+ if ($name == 'HELO') {
+ return $this->server_caps['EHLO'];
+ }
+ if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) {
+ return false;
+ }
+ $this->error = array('HELO handshake was used. Client knows nothing about server extensions');
+ return null;
+ }
+
+ return $this->server_caps[$name];
+ }
+
+ /**
+ * Get the last reply from the server.
+ * @access public
+ * @return string
+ */
+ public function getLastReply()
+ {
+ return $this->last_reply;
+ }
+
+ /**
+ * Read the SMTP server's response.
+ * Either before eof or socket timeout occurs on the operation.
+ * With SMTP we can tell if we have more lines to read if the
+ * 4th character is '-' symbol. If it is a space then we don't
+ * need to read anything else.
+ * @access protected
+ * @return string
+ */
+ protected function get_lines()
+ {
+ // If the connection is bad, give up straight away
+ if (!is_resource($this->smtp_conn)) {
+ return '';
+ }
+ $data = '';
+ $endtime = 0;
+ stream_set_timeout($this->smtp_conn, $this->Timeout);
+ if ($this->Timelimit > 0) {
+ $endtime = time() + $this->Timelimit;
+ }
+ while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
+ $str = @fgets($this->smtp_conn, 515);
+ $this->edebug("SMTP -> get_lines(): \$data was \"$data\"", self::DEBUG_LOWLEVEL);
+ $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL);
+ $data .= $str;
+ $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
+ // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen
+ if ((isset($str[3]) and $str[3] == ' ')) {
+ break;
+ }
+ // Timed-out? Log and break
+ $info = stream_get_meta_data($this->smtp_conn);
+ if ($info['timed_out']) {
+ $this->edebug(
+ 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
+ self::DEBUG_LOWLEVEL
+ );
+ break;
+ }
+ // Now check if reads took too long
+ if ($endtime and time() > $endtime) {
+ $this->edebug(
+ 'SMTP -> get_lines(): timelimit reached ('.
+ $this->Timelimit . ' sec)',
+ self::DEBUG_LOWLEVEL
+ );
+ break;
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Enable or disable VERP address generation.
+ * @param boolean $enabled
+ */
+ public function setVerp($enabled = false)
+ {
+ $this->do_verp = $enabled;
+ }
+
+ /**
+ * Get VERP address generation mode.
+ * @return boolean
+ */
+ public function getVerp()
+ {
+ return $this->do_verp;
+ }
+
+ /**
+ * Set debug output method.
+ * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it.
+ */
+ public function setDebugOutput($method = 'echo')
+ {
+ $this->Debugoutput = $method;
+ }
+
+ /**
+ * Get debug output method.
+ * @return string
+ */
+ public function getDebugOutput()
+ {
+ return $this->Debugoutput;
+ }
+
+ /**
+ * Set debug output level.
+ * @param integer $level
+ */
+ public function setDebugLevel($level = 0)
+ {
+ $this->do_debug = $level;
+ }
+
+ /**
+ * Get debug output level.
+ * @return integer
+ */
+ public function getDebugLevel()
+ {
+ return $this->do_debug;
+ }
+
+ /**
+ * Set SMTP timeout.
+ * @param integer $timeout
+ */
+ public function setTimeout($timeout = 0)
+ {
+ $this->Timeout = $timeout;
+ }
+
+ /**
+ * Get SMTP timeout.
+ * @return integer
+ */
+ public function getTimeout()
+ {
+ return $this->Timeout;
+ }
+}
diff --git a/inc/PHPMailer/extras/EasyPeasyICS.php b/inc/PHPMailer/extras/EasyPeasyICS.php
new file mode 100644
index 0000000..1d50b01
--- /dev/null
+++ b/inc/PHPMailer/extras/EasyPeasyICS.php
@@ -0,0 +1,148 @@
+
+ * @author Manuel Reinhard
+ *
+ * Built with inspiration from
+ * http://stackoverflow.com/questions/1463480/how-can-i-use-php-to-dynamically-publish-an-ical-file-to-be-read-by-google-calend/1464355#1464355
+ * History:
+ * 2010/12/17 - Manuel Reinhard - when it all started
+ * 2014 PHPMailer project becomes maintainer
+ */
+
+/**
+ * Class EasyPeasyICS.
+ * Simple ICS data generator
+ * @package phpmailer
+ * @subpackage easypeasyics
+ */
+class EasyPeasyICS
+{
+ /**
+ * The name of the calendar
+ * @type string
+ */
+ protected $calendarName;
+ /**
+ * The array of events to add to this calendar
+ * @type array
+ */
+ protected $events = array();
+
+ /**
+ * Constructor
+ * @param string $calendarName
+ */
+ public function __construct($calendarName = "")
+ {
+ $this->calendarName = $calendarName;
+ }
+
+ /**
+ * Add an event to this calendar.
+ * @param string $start The start date and time as a unix timestamp
+ * @param string $end The end date and time as a unix timestamp
+ * @param string $summary A summary or title for the event
+ * @param string $description A description of the event
+ * @param string $url A URL for the event
+ * @param string $uid A unique identifier for the event - generated automatically if not provided
+ * @return array An array of event details, including any generated UID
+ */
+ public function addEvent($start, $end, $summary = '', $description = '', $url = '', $uid = '')
+ {
+ if (empty($uid)) {
+ $uid = md5(uniqid(mt_rand(), true)) . '@EasyPeasyICS';
+ }
+ $event = array(
+ 'start' => gmdate('Ymd', $start) . 'T' . gmdate('His', $start) . 'Z',
+ 'end' => gmdate('Ymd', $end) . 'T' . gmdate('His', $end) . 'Z',
+ 'summary' => $summary,
+ 'description' => $description,
+ 'url' => $url,
+ 'uid' => $uid
+ );
+ $this->events[] = $event;
+ return $event;
+ }
+
+ /**
+ * @return array Get the array of events.
+ */
+ public function getEvents()
+ {
+ return $this->events;
+ }
+
+ /**
+ * Clear all events.
+ */
+ public function clearEvents()
+ {
+ $this->events = array();
+ }
+
+ /**
+ * Get the name of the calendar.
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->calendarName;
+ }
+
+ /**
+ * Set the name of the calendar.
+ * @param $name
+ */
+ public function setName($name)
+ {
+ $this->calendarName = $name;
+ }
+
+ /**
+ * Render and optionally output a vcal string.
+ * @param bool $output Whether to output the calendar data directly (the default).
+ * @return string The complete rendered vlal
+ */
+ public function render($output = true)
+ {
+ //Add header
+ $ics = 'BEGIN:VCALENDAR
+METHOD:PUBLISH
+VERSION:2.0
+X-WR-CALNAME:' . $this->calendarName . '
+PRODID:-//hacksw/handcal//NONSGML v1.0//EN';
+
+ //Add events
+ foreach ($this->events as $event) {
+ $ics .= '
+BEGIN:VEVENT
+UID:' . $event['uid'] . '
+DTSTAMP:' . gmdate('Ymd') . 'T' . gmdate('His') . 'Z
+DTSTART:' . $event['start'] . '
+DTEND:' . $event['end'] . '
+SUMMARY:' . str_replace("\n", "\\n", $event['summary']) . '
+DESCRIPTION:' . str_replace("\n", "\\n", $event['description']) . '
+URL;VALUE=URI:' . $event['url'] . '
+END:VEVENT';
+ }
+
+ //Add footer
+ $ics .= '
+END:VCALENDAR';
+
+ if ($output) {
+ //Output
+ $filename = $this->calendarName;
+ //Filename needs quoting if it contains spaces
+ if (strpos($filename, ' ') !== false) {
+ $filename = '"'.$filename.'"';
+ }
+ header('Content-type: text/calendar; charset=utf-8');
+ header('Content-Disposition: inline; filename=' . $filename . '.ics');
+ echo $ics;
+ }
+ return $ics;
+ }
+}
diff --git a/inc/PHPMailer/extras/README.md b/inc/PHPMailer/extras/README.md
new file mode 100644
index 0000000..dac79e0
--- /dev/null
+++ b/inc/PHPMailer/extras/README.md
@@ -0,0 +1,17 @@
+#PHPMailer Extras
+
+These classes provide optional additional functions to PHPMailer.
+
+These are not loaded by the PHPMailer autoloader, so in some cases you may need to `require` them yourself before using them.
+
+##EasyPeasyICS
+
+This class was originally written by Manuel Reinhard and provides a simple means of generating ICS/vCal files that are used in sending calendar events. PHPMailer does not use it directly, but you can use it to generate content appropriate for placing in the `Ical` property of PHPMailer. The PHPMailer project is now its official home as Manuel has given permission for that and is no longer maintaining it himself.
+
+##htmlfilter
+
+This class by Konstantin Riabitsev and Jim Jagielski implements HTML filtering to remove potentially malicious tags, such as `
+
+
+
+
diff --git a/js/main.js b/js/main.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/js/main.js
@@ -0,0 +1 @@
+
diff --git a/js/vendor/jquery-1.10.2.min.js b/js/vendor/jquery-1.10.2.min.js
new file mode 100644
index 0000000..da41706
--- /dev/null
+++ b/js/vendor/jquery-1.10.2.min.js
@@ -0,0 +1,6 @@
+/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
+//@ sourceMappingURL=jquery-1.10.2.min.map
+*/
+(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,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"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window);
diff --git a/js/vendor/modernizr-2.6.2.min.js b/js/vendor/modernizr-2.6.2.min.js
new file mode 100644
index 0000000..f65d479
--- /dev/null
+++ b/js/vendor/modernizr-2.6.2.min.js
@@ -0,0 +1,4 @@
+/* Modernizr 2.6.2 (Custom Build) | MIT & BSD
+ * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load
+ */
+;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f
+* Version 2.0.5 - built Thu Aug 28 2014 11:33:36
+* MIT Licensed
+*
+*/
+!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){"undefined"==typeof a&&"undefined"!=typeof window.jQuery&&(a=window.jQuery);var b={attr:function(a,b,c){var d,e={},f=this.msieversion(),g=new RegExp("^"+b,"i");if("undefined"==typeof a||"undefined"==typeof a[0])return{};for(var h in a[0].attributes)if(d=a[0].attributes[h],"undefined"!=typeof d&&null!==d&&(!f||f>=8||d.specified)&&g.test(d.name)){if("undefined"!=typeof c&&new RegExp(c+"$","i").test(d.name))return!0;e[this.camelize(d.name.replace(b,""))]=this.deserializeValue(d.value)}return"undefined"==typeof c?e:!1},setAttr:function(a,b,c,d){a[0].setAttribute(this.dasherize(b+c),String(d))},get:function(a,b){for(var c=0,d=(b||"").split(".");this.isObject(a)||this.isArray(a);)if(a=a[d[c++]],c===d.length)return a;return void 0},hash:function(a){return String(Math.random()).substring(2,a?a+2:9)},isArray:function(a){return"[object Array]"===Object.prototype.toString.call(a)},isObject:function(a){return a===Object(a)},deserializeValue:function(b){var c;try{return b?"true"==b||("false"==b?!1:"null"==b?null:isNaN(c=Number(b))?/^[\[\{]/.test(b)?a.parseJSON(b):b:c):b}catch(d){return b}},camelize:function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},dasherize:function(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()},msieversion:function(){var a=window.navigator.userAgent,b=a.indexOf("MSIE ");return b>0||navigator.userAgent.match(/Trident.*rv\:11\./)?parseInt(a.substring(b+5,a.indexOf(".",b)),10):0}},c={namespace:"data-parsley-",inputs:"input, textarea, select",excluded:"input[type=button], input[type=submit], input[type=reset], input[type=hidden]",priorityEnabled:!0,uiEnabled:!0,validationThreshold:3,focus:"first",trigger:!1,errorClass:"parsley-error",successClass:"parsley-success",classHandler:function(){},errorsContainer:function(){},errorsWrapper:'
',errorTemplate:""},d=function(){};d.prototype={asyncSupport:!1,actualizeOptions:function(){return this.options=this.OptionsFactory.get(this),this},validateThroughValidator:function(a,b,c){return window.ParsleyValidator.validate.apply(window.ParsleyValidator,[a,b,c])},subscribe:function(b,c){return a.listenTo(this,b.toLowerCase(),c),this},unsubscribe:function(b){return a.unsubscribeTo(this,b.toLowerCase()),this},reset:function(){if("ParsleyForm"!==this.__class__)return a.emit("parsley:field:reset",this);for(var b=0;b=0;l--)"Required"!==k[l].__class__||(i=k[l].requiresValidation(b));if(this.has(h,a)||this.options.strict||i)try{this.has(h,this.options.strict||i?a:void 0)||(new e).HaveProperty(h).validate(a),c=this._check(h,a[h],b),(g(c)&&c.length>0||!g(c)&&!f(c))&&(d[h]=c)}catch(m){d[h]=m}}return f(d)?!0:d},add:function(a,b){if(b instanceof e||g(b)&&b[0]instanceof e)return this.nodes[a]=b,this;if("object"==typeof b&&!g(b))return this.nodes[a]=b instanceof c?b:new c(b),this;throw new Error("Should give an Assert, an Asserts array, a Constraint",b)},has:function(a,b){return b="undefined"!=typeof b?b:this.nodes,"undefined"!=typeof b[a]},get:function(a,b){return this.has(a)?this.nodes[a]:b||null},remove:function(a){var b=[];for(var c in this.nodes)c!==a&&(b[c]=this.nodes[c]);return this.nodes=b,this},_bootstrap:function(a){if(a instanceof c)return this.nodes=a.nodes;for(var b in a)this.add(b,a[b])},_check:function(a,b,d){if(this.nodes[a]instanceof e)return this._checkAsserts(b,[this.nodes[a]],d);if(g(this.nodes[a]))return this._checkAsserts(b,this.nodes[a],d);if(this.nodes[a]instanceof c)return this.nodes[a].check(b,d);throw new Error("Invalid node",this.nodes[a])},_checkAsserts:function(a,b,c){for(var d,e=[],f=0;f0},addGroup:function(a){return g(a)?this.addGroups(a):(this.hasGroup(a)||this.groups.push(a),this)},removeGroup:function(a){for(var b=[],c=0;c=a)throw new d(this,a,{threshold:this.threshold});return!0},this},GreaterThanOrEqual:function(a){if(this.__class__="GreaterThanOrEqual","undefined"==typeof a)throw new Error("Should give a threshold value");return this.threshold=a,this.validate=function(a){if(""===a||isNaN(Number(a)))throw new d(this,a,{value:b.errorCode.must_be_a_number});if(this.threshold>a)throw new d(this,a,{threshold:this.threshold});return!0},this},InstanceOf:function(a){if(this.__class__="InstanceOf","undefined"==typeof a)throw new Error("InstanceOf must be instanciated with a value");return this.classRef=a,this.validate=function(a){if(!0!=a instanceof this.classRef)throw new d(this,a,{classRef:this.classRef});return!0},this},Length:function(a){if(this.__class__="Length",!a.min&&!a.max)throw new Error("Lenth assert must be instanciated with a { min: x, max: y } object");return this.min=a.min,this.max=a.max,this.validate=function(a){if("string"!=typeof a&&!g(a))throw new d(this,a,{value:b.errorCode.must_be_a_string_or_array});if("undefined"!=typeof this.min&&this.min===this.max&&a.length!==this.min)throw new d(this,a,{min:this.min,max:this.max});if("undefined"!=typeof this.max&&a.length>this.max)throw new d(this,a,{max:this.max});if("undefined"!=typeof this.min&&a.length>>0;if(0===c)return-1;var d=0;if(arguments.length>1&&(d=Number(arguments[1]),d!=d?d=0:0!==d&&1/0!=d&&d!=-1/0&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1});var f=function(a){for(var b in a)return!1;return!0},g=function(a){return"[object Array]"===Object.prototype.toString.call(a)};return"function"==typeof define&&define.amd?define("vendors/validator.js/dist/validator",[],function(){return a}):"undefined"!=typeof module&&module.exports?module.exports=a:window["undefined"!=typeof validatorjs_ns?validatorjs_ns:"Validator"]=a,a}();e="undefined"!=typeof e?e:"undefined"!=typeof module?module.exports:null;var f=function(a,b){this.__class__="ParsleyValidator",this.Validator=e,this.locale="en",this.init(a||{},b||{})};f.prototype={init:function(b,c){this.catalog=c;for(var d in b)this.addValidator(d,b[d].fn,b[d].priority,b[d].requirementsTransformer);a.emit("parsley:validator:init")},setLocale:function(a){if("undefined"==typeof this.catalog[a])throw new Error(a+" is not available in the catalog");return this.locale=a,this},addCatalog:function(a,b,c){return"object"==typeof b&&(this.catalog[a]=b),!0===c?this.setLocale(a):this},addMessage:function(a,b,c){return"undefined"==typeof this.catalog[a]&&(this.catalog[a]={}),this.catalog[a][b.toLowerCase()]=c,this},validate:function(){return(new this.Validator.Validator).validate.apply(new e.Validator,arguments)},addValidator:function(b,c,d,f){return this.validators[b.toLowerCase()]=function(b){return a.extend((new e.Assert).Callback(c,b),{priority:d,requirementsTransformer:f})},this},updateValidator:function(a,b,c,d){return this.addValidator(a,b,c,d)},removeValidator:function(a){return delete this.validators[a],this},getErrorMessage:function(a){var b;return b="type"===a.name?this.catalog[this.locale][a.name][a.requirements]:this.formatMessage(this.catalog[this.locale][a.name],a.requirements),""!==b?b:this.catalog[this.locale].defaultMessage},formatMessage:function(a,b){if("object"==typeof b){for(var c in b)a=this.formatMessage(a,b[c]);return a}return"string"==typeof a?a.replace(new RegExp("%s","i"),b):""},validators:{notblank:function(){return a.extend((new e.Assert).NotBlank(),{priority:2})},required:function(){return a.extend((new e.Assert).Required(),{priority:512})},type:function(b){var c;switch(b){case"email":c=(new e.Assert).Email();break;case"range":case"number":c=(new e.Assert).Regexp("^-?(?:\\d+|\\d{1,3}(?:,\\d{3})+)?(?:\\.\\d+)?$");break;case"integer":c=(new e.Assert).Regexp("^-?\\d+$");break;case"digits":c=(new e.Assert).Regexp("^\\d+$");break;case"alphanum":c=(new e.Assert).Regexp("^\\w+$","i");break;case"url":c=(new e.Assert).Regexp("(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,4}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)","i");break;default:throw new Error("validator type `"+b+"` is not supported")}return a.extend(c,{priority:256})},pattern:function(b){var c="";return/^\/.*\/(?:[gimy]*)$/.test(b)&&(c=b.replace(/.*\/([gimy]*)$/,"$1"),b=b.replace(new RegExp("^/(.*?)/"+c+"$"),"$1")),a.extend((new e.Assert).Regexp(b,c),{priority:64})},minlength:function(b){return a.extend((new e.Assert).Length({min:b}),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},maxlength:function(b){return a.extend((new e.Assert).Length({max:b}),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},length:function(b){return a.extend((new e.Assert).Length({min:b[0],max:b[1]}),{priority:32})},mincheck:function(a){return this.minlength(a)},maxcheck:function(a){return this.maxlength(a)},check:function(a){return this.length(a)},min:function(b){return a.extend((new e.Assert).GreaterThanOrEqual(b),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},max:function(b){return a.extend((new e.Assert).LessThanOrEqual(b),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},range:function(b){return a.extend((new e.Assert).Range(b[0],b[1]),{priority:32,requirementsTransformer:function(){for(var a=0;a0?this._errorClass(a):this._resetClass(a)},manageErrorsMessages:function(b,c){if("undefined"==typeof b.options.errorsMessagesDisabled){if("undefined"!=typeof b.options.errorMessage)return c.added.length||c.kept.length?(0===b._ui.$errorsWrapper.find(".parsley-custom-error-message").length&&b._ui.$errorsWrapper.append(a(b.options.errorTemplate).addClass("parsley-custom-error-message")),b._ui.$errorsWrapper.addClass("filled").find(".parsley-custom-error-message").html(b.options.errorMessage)):b._ui.$errorsWrapper.removeClass("filled").find(".parsley-custom-error-message").remove();for(var d=0;d0&&"undefined"==typeof a.fields[b].options.noFocus){if("first"===a.options.focus)return a._focusedField=a.fields[b].$element,a._focusedField.focus();a._focusedField=a.fields[b].$element}return null===a._focusedField?null:a._focusedField.focus()},_getErrorMessage:function(a,b){var c=b.name+"Message";return"undefined"!=typeof a.options[c]?window.ParsleyValidator.formatMessage(a.options[c],b.requirements):window.ParsleyValidator.getErrorMessage(b)},_diff:function(a,b,c){for(var d=[],e=[],f=0;f0&&this.validationResult&&(this.validationResult=!1));return a.emit("parsley:form:validated",this),this.validationResult},isValid:function(a,b){this._refreshFields();for(var c=0;c1){var c=[];return this.each(function(){c.push(a(this).parsley(b))}),c}return a(this).length?new o(this,b):void(window.console&&window.console.warn&&window.console.warn("You must bind Parsley on an existing element."))},window.ParsleyUI="function"==typeof b.get(window,"ParsleyConfig.ParsleyUI")?(new window.ParsleyConfig.ParsleyUI).listen():(new g).listen(),"undefined"==typeof window.ParsleyExtend&&(window.ParsleyExtend={}),"undefined"==typeof window.ParsleyConfig&&(window.ParsleyConfig={}),window.Parsley=window.psly=o,window.ParsleyUtils=b,window.ParsleyValidator=new f(window.ParsleyConfig.validators,window.ParsleyConfig.i18n),!1!==b.get(window,"ParsleyConfig.autoBind")&&a(document).ready(function(){a("[data-parsley-validate]").length&&a("[data-parsley-validate]").parsley()})});
\ No newline at end of file
diff --git a/merci.php b/merci.php
new file mode 100644
index 0000000..cf8c21a
--- /dev/null
+++ b/merci.php
@@ -0,0 +1,123 @@
+isSMTP();
+$mail->CharSet = "UTF-8";
+//Enable SMTP debugging
+// 0 = off (for production use)
+// 1 = client messages
+// 2 = client and server messages
+//$mail->SMTPDebug = 2;
+//Ask for HTML-friendly debug output
+//$mail->Debugoutput = 'html';
+// To load the French version
+$mail->setLanguage('fr', 'inc/PHPMailer/language/');
+$mail->Host = 'smtp.ouvaton.coop';
+$mail->SMTPAuth = true;
+$mail->SMTPKeepAlive = true;
+$mail->Username = '';
+$mail->Password = '';
+$mail->SMTPSecure = 'tls';
+$mail->Port = 587;
+
+$mail->From = '';
+$mail->FromName = 'Ouvaton.coop AG 2019';
+$mail->addAddress($_SESSION['email']);
+$mail->addReplyTo('');
+
+$mail->isHTML(true);
+$mail->Subject = 'Ouvaton AG 2019 : merci pour votre vote !';
+
+// On prépare le contenu du mail
+$body = "
+
+
+ Ouvaton.coop AG 2019
+
+
+
Merci d'avoir voté pour notre AG 2019 !
+
+
Une clé de vérification a été générée pour vous permettre de vérifier l'intégrité de votre vote. Cette clé est unique et connue de vous seul-e.
+
+
Clé de vérification : ".$_SESSION['cle']."
+
+
+
Vous pouvez vous connecter sur https://ag.ouvaton.coop/ pour vérifier que votre bulletin est resté conforme à votre vote.
+
+
Coopérativement,
+ Le Président du Directoire
+
Ouvaton Coopérative SA à directoire et conseil de surveillance à capital variable Siège Social 16 bis rue d’Odessa 75014 PARIS R.C.S. Paris 438 168 718
+
+
+";
+$mail->msgHTML($body);
+$mail->send();
+//if(!$mail->send()) {
+// echo 'Message could not be sent.';
+// echo 'Mailer Error: ' . $mail->ErrorInfo;
+//} else {
+// echo 'Message has been sent';
+//}
+
+?>
+
+
+
+
+
+
+
+
+
+
Merci de votre participation , chaque vote pour notre Assemblée Générale est important.
+
Votre clé de vérification a été générée pour vous permettre de vérifier l'intégrité de votre vote. Cette clé est unique et connue de vous seul-e.
+
Votre clé de vérification est :
+
+
Vous allez recevoir un courriel contenant cette clé de vérification.
+ Nous vous recommandons de le conserver jusqu'au jour de notre AG ou d'imprimer cette page,
+ pour ne pas perdre votre clé.
+
+
Vous pouvez vous connecter sur https://ag.ouvaton.coop/ pour vérifier que
+ votre bulletin est resté conforme à votre vote.
+
Votre vote est enregistré et vous êtes maintenant déconnecté-e.
+
+
+
+
+
+
+
+
+
+
diff --git a/vote.php b/vote.php
new file mode 100644
index 0000000..3b918c8
--- /dev/null
+++ b/vote.php
@@ -0,0 +1,256 @@
+prepare($sql);
+// On regarde le nombre de résolution d'AGO qui sera important par la suite pour créer des variables de sessions et le formulaire de vote
+if ($ago) {
+ $option = 'resolution_ago_nb';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ $stmt->execute();
+ $results = $stmt->fetch();
+ $resolution_ago_nb = $results['option_value'];
+ $_SESSION['resolution_ago_nb'] = $resolution_ago_nb;
+}
+// On regarde le nombre de résolution d'AGE qui sera important par la suite pour créer des variables de sessions et le formulaire de vote
+if ($age) {
+ $option = 'resolution_age_nb';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ $stmt->execute();
+ $results = $stmt->fetch();
+ $resolution_age_nb = $results['option_value'];
+ $_SESSION['resolution_age_nb'] = $resolution_age_nb;
+}
+// On regarde combien de postes au CS sont à pourvoir
+if ($cs) {
+ $option = 'cs_nb_poste';
+ $stmt->bindParam(':option', $option, PDO::PARAM_STR);
+ $stmt->execute();
+ $results = $stmt->fetch();
+ $cs_nb_poste = $results['option_value'];
+}
+
+// Le client a cliqué sur le bouton envoyer ?
+if (!empty ($_POST['voted']) && ($_POST['voted'] == true)) {
+ // Alors on regarde si le formulaire est bien renseigné
+ if ($ago) {
+ for ($i = 1; $i <= $resolution_ago_nb; $i++) {
+ if (!isset($_POST['QAGO'.$i])) {$missingform = "
Merci de remplir tous les champs du formulaire1.
";}
+ }
+ }
+ if ($age) {
+ for ($i = 1; $i <= $resolution_age_nb; $i++) {
+ if (!isset($_POST['QAGE'.$i])) {$missingform = "
Merci de remplir tous les champs du formulaire2.
";}
+ }
+ }
+ // Si tout les inputs du formulaire sont renseignés
+ if (!isset ($missingform)) {
+ // En cas d'absention au vote du CS
+ if ($cs) {
+ if (isset($_POST['csabst'])) {
+ // On créé une variable de session pour chaque résolution AGO et AGE et pour le CS
+ if ($ago) {
+ for ($i = 1; $i <= $resolution_ago_nb; $i++) {
+ $_SESSION['resolution_ago_'.$i] = $_POST['QAGO'.$i];
+ }
+ }
+ if ($age) {
+ for ($i = 1; $i <= $resolution_age_nb; $i++) {
+ $_SESSION['resolution_age_'.$i] = $_POST['QAGE'.$i];
+ }
+ }
+ $vote_cs = "Abstention";
+ $_SESSION['vote_cs'] = $vote_cs;
+ // On redirige
+ header('Location: finalisation.php');
+ } elseif (isset($_POST['CS'])) {
+ // En cas de vote pour le CS
+ $nb_vote_cs = count($_POST['CS']);
+ // Vérification du nombre de candidats coché
+ // Si le nombre est trop faible, on retourne sur le formulaire avec un message d'erreur
+ if ($nb_vote_cs < $cs_nb_poste) {
+ header('Location: vote.php?message=true');
+ exit;
+ }
+ // On sépare chaque nom de candidats par une ,
+ $vote_cs = implode(",", $_POST['CS']);
+ // On créé une variable de session pour chaque résolution AGO et AGE, et pour le CS
+ if ($ago) {
+ for ($i = 1; $i <= $resolution_ago_nb; $i++) {
+ $_SESSION['resolution_ago_'.$i] = $_POST['QAGO'.$i];
+ }
+ }
+ if ($age) {
+ for ($i = 1; $i <= $resolution_age_nb; $i++) {
+ $_SESSION['resolution_age_'.$i] = $_POST['QAGE'.$i];
+ }
+ }
+ $_SESSION['vote_cs'] = $vote_cs;
+ // On redirige
+ header('Location: finalisation.php');
+ }
+ }
+ if ($ago) {
+ for ($i = 1; $i <= $resolution_ago_nb; $i++) {
+ $_SESSION['resolution_ago_'.$i] = $_POST['QAGO'.$i];
+ }
+ }
+ if ($age) {
+ for ($i = 1; $i <= $resolution_age_nb; $i++) {
+ $_SESSION['resolution_age_'.$i] = $_POST['QAGE'.$i];
+ }
+ }
+ // On redirige
+ header('Location: finalisation.php');
+ }
+}
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+