voting options and new motion links for board members only
[cacert-boardvoting.git] / database.php
1 <?php
2 class DB {
3 var $board = "cacert-board@lists.cacert.org";
4 var $notices = "testsympa@lists.cacert.org";
5
6 function __construct() {
7 $this->dbh = new PDO("sqlite:".dirname(__FILE__)."/database.sqlite");
8 $this->statement = array();
9 $this->statement['list decisions'] = $this->dbh->prepare("SELECT decisions.id AS id, decisions.tag AS tag, voters.name AS proposer, decisions.proposed, decisions.title, decisions.content, decisions.votetype, decisions.status, decisions.due, decisions.modified, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=1) AS ayes, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=-1) AS nayes, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=0) AS abstains FROM decisions, voters WHERE decisions.proponent=voters.id ORDER BY proposed DESC LIMIT 10 OFFSET 10 * (:page - 1);");
10 $this->statement['list my unvoted decisions'] = $this->dbh->prepare("SELECT * FROM (SELECT decisions.id AS id, decisions.tag AS tag, voters.name AS proposer,decisions.proposed AS proposed, decisions.title AS title, decisions.content AS content, decisions.votetype AS votetype, decisions.status AS status, decisions.due AS due, decisions.modified AS modified,(SELECT COUNT(*) AS ayes FROM votes WHERE decision=decisions.id AND vote=1), (SELECT COUNT(*) AS nayes FROM votes WHERE decision=decisions.id AND vote=-1), (SELECT COUNT(*) AS abstains FROM votes WHERE decision=decisions.id AND vote=0) FROM decisions, voters WHERE decisions.proponent=voters.id AND decisions.status=0) WHERE NOT EXISTS (SELECT vote FROM votes WHERE votes.decision=id AND votes.voter=:id) ORDER BY proposed DESC LIMIT 10 OFFSET 10 * (:page - 1);");
11 $this->statement['list decision'] = $this->dbh->prepare("SELECT decisions.id AS id, decisions.tag AS tag, voters.name AS proposer, decisions.proposed, decisions.title, decisions.content, decisions.votetype, decisions.status, decisions.due, decisions.modified, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=1) AS ayes, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=-1) AS nayes, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=0) AS abstains FROM decisions, voters WHERE decisions.proponent=voters.id AND decisions.tag=:id ORDER BY proposed DESC;");
12 $this->statement['closed decisions'] = $this->dbh->prepare("SELECT decisions.id, decisions.tag, voters.name AS proposer, decisions.proposed, decisions.title, decisions.content, decisions.votetype, decisions.status, decisions.due, decisions.modified, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=1) AS ayes, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=-1) AS nayes, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=0) AS abstains FROM decisions, voters WHERE decisions.proponent=voters.id AND decisions.status=0 AND datetime('now','utc') > datetime(due);");
13 $this->statement['get decision'] = $this->dbh->prepare("SELECT decisions.id, decisions.tag, decisions.proponent, voters.name AS proposer, decisions.proposed, decisions.title, decisions.content, decisions.votetype, decisions.status, decisions.due, decisions.modified, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=1) AS ayes, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=-1) AS nayes, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=0) AS abstains FROM decisions, voters WHERE decisions.proponent=voters.id AND decisions.id=:decision;");
14 $this->statement['get new decision'] = $this->dbh->prepare("SELECT decisions.id, decisions.tag, decisions.proponent, voters.name AS proposer, decisions.proposed, decisions.title, decisions.content, decisions.votetype, decisions.status, decisions.due, decisions.modified, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=1) AS ayes, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=-1) AS nayes, (SELECT COUNT(*) FROM votes WHERE decision=decisions.id AND vote=0) AS abstains FROM decisions, voters WHERE decisions.proponent=voters.id AND decisions.id=last_insert_rowid();");
15 $this->statement['get voter'] = $this->dbh->prepare("SELECT voters.id, voters.name FROM voters, emails WHERE voters.id=emails.voter AND emails.address=? AND voters.enabled=1");
16 $this->statement['get voter by id'] = $this->dbh->prepare("SELECT voters.id, voters.name FROM voters WHERE id=:id;");
17 $this->statement['get voters'] = $this->dbh->prepare("SELECT voters.id, voters.name FROM voters WHERE voters.enabled=1 ORDER BY name ASC;");
18 $this->statement['del vote'] = $this->dbh->prepare("DELETE FROM votes WHERE decision=:decision AND voter=:voter;");
19 $this->statement['do vote'] = $this->dbh->prepare("INSERT INTO votes (decision, voter, vote, voted, notes) VALUES (:decision, :voter, :vote, datetime('now','utc'), :notes);");
20 $this->statement['stats'] = $this->dbh->prepare("SELECT COUNT(*) AS voters FROM voters WHERE enabled=1;");
21 $this->statement['list votes'] = $this->dbh->prepare("SELECT voters.name AS name, votes.vote AS vote FROM voters,votes WHERE voters.id=votes.voter AND votes.decision=:id;");
22 $this->statement['create decision'] = $this->dbh->prepare("INSERT INTO decisions (proposed, proponent, title, content, votetype, status, due, modified) VALUES (datetime('now','utc'), :proponent, :title, :content, :votetype, 0, datetime('now','utc', :due), datetime('now','utc'));");
23 $this->statement['post create'] = $this->dbh->prepare(" UPDATE decisions SET tag='m' || strftime('%Y%m%d','now') || '.' || id WHERE id=last_insert_rowid();");
24 $this->statement['update decision'] = $this->dbh->prepare("UPDATE decisions SET proposed=datetime('now','utc'), proponent=:proponent, title=:title, content=:content, votetype=:votetype, status=0, due=datetime('now','utc',:due), modified=datetime('now','utc') WHERE id=:id;");
25 $this->statement['close decision'] = $this->dbh->prepare("UPDATE decisions SET status=:status, modified=datetime('now','utc') WHERE id=:decision");
26 }
27 function getStatement($name) {
28 return $this->statement[$name];
29 }
30 function closeVotes() {
31 $stmt = $this->getStatement("closed decisions");
32 $upd = $this->getStatement("close decision");
33 if ($stmt->execute()) {
34 while ($decision = $stmt->fetch()) {
35 switch ($decision['votetype']) {
36 case 0: // motion
37 $quorum = 3; $majority = 50; break;
38 case 1: // veto
39 default:
40 $quorum = 1; $majority = 99; break;
41 }
42 $votes = $decision['ayes'] + $decision['nayes'] + $decision['abstains'];
43 if ($votes < $quorum) {
44 $decision['status'] = -1;
45 } else {
46 $votes = $decision['ayes'] + $decision['nayes'];
47 if (($decision['ayes'] / $votes) > ($majority / 100)) {
48 $decision['status'] = 1;
49 } else {
50 $decision['status'] = -1;
51 }
52 }
53 $upd->bindParam(":decision",$decision['id']);
54 $upd->bindParam(":status",$decision['status']);
55 $upd->execute();
56 $state = $decision['status']==1?"accepted":"declined";
57 $tag = $decision['tag'];
58 $title = $decision['title'];
59 $content = $decision['content'];
60 $votetype = !$decision['votetype']?'motion':'veto';
61 $ayes = $decision['ayes'];
62 $nayes = $decision['nayes'];
63 $abstains = $decision['abstains'];
64 $totalvotes = $decision['ayes']+$decision['nayes'];
65 if ($totalvotes <= 0) $percent = 0;
66 else $percent = $decision['ayes'] * 100 / $totalvotes;
67 $body = <<<BODY
68 Dear Board,
69
70 The motion with the identifier $tag has been $state.
71
72 Motion:
73 $title
74 $content
75
76 Vote type: $votetype
77
78 Ayes: $ayes
79 Nayes: $nayes
80 Abstentions: $abstains
81
82 Percentage: $percent%
83
84 Kind regards,
85 the voting system.
86
87 BODY;
88 $this->notify("Re: ".$decision['tag']." - ".$decision['title']." - finalised",$body,$decision['tag']);
89 }
90 }
91 }
92 function notify($subject,$body,$tag,$first=FALSE)
93 {
94 if ($first) {
95 $header = "Message-id: <".$tag.">\r\n";
96 } else {
97 $header = "References: <".$tag.">\r\nIn-reply-to: <".$tag.">\r\n";
98 }
99 mail($this->board, mb_encode_mimeheader($subject,"UTF-8", "B", "\n"),$body,$header."From: Voting System <returns@cacert.org>");
100 //mail("testsympa@lists.cacert.org",$subject,$body,"From: Voting System <returns@cacert.org>");
101 }
102 function vote_notify($subject,$body,$tag)
103 {
104 $header = "References: <".$tag.">\r\nIn-reply-to: <".$tag.">\r\n";
105 mail($this->notices, mb_encode_mimeheader($subject,"UTF-8", "B", "\n"),$body,$header."From: Voting System <returns@cacert.org>");
106 }
107 function auth()
108 {
109 $stmt = $this->getStatement("get voter");
110 $stmt->execute(array($_SERVER['REMOTE_USER']));
111 $user = $stmt->fetch();
112 if ($user) return $user;
113 if ($_SERVER['SSL_CLIENT_S_DN_EMAIL']) {
114 $stmt->execute(array($_SERVER['SSL_CLIENT_S_DN_EMAIL']));
115 $user = $stmt->fetch();
116 if ($user) return $user;
117 }
118 $d=0;
119 while ($email=$_SERVER["SSL_CLIENT_S_DN_EMAIL_$d"]) {
120 $stmt->execute(array($email));
121 $user = $stmt->fetch();
122 if ($user) return $user;
123 ++$d;
124 }
125 if (preg_match_all('/\/emailAddress=([^\/]*)/',$dn,$reg,PREG_SET_ORDER)) {
126 foreach ($reg as $emailarr) {
127 $stmt->execute(array($emailarr[1]));
128 $user = $stmt->fetch();
129 if ($user) return $user;
130 }
131 }
132 if ($_SERVER['SSL_CLIENT_CERT']) {
133 # subjectAltName unpresented by Apache http://httpd.apache.org/docs/trunk/mod/mod_ssl.html
134 # subjectAltName http://tools.ietf.org/html/rfc5280#section-4.2.1.6
135 # WARNING WARNING openssl_x509_parse is an unstable PHP API
136 $x509 = openssl_x509_parse($_SERVER['SSL_CLIENT_CERT']);
137 $subjectAltName = $x509['extensions']['subjectAltName']; // going off https://foaf.me/testSSL.php
138 #print_r(split("[, ]",$subjectAltName));
139 #print_r($x509);
140 #echo $subjectAltName;
141 if (preg_match_all('/email:([^, ]*)/',$subjectAltName,$reg,PREG_SET_ORDER)) {
142 foreach ($reg as $emailarr) {
143 $stmt->execute(array($emailarr[1]));
144 $user = $stmt->fetch();
145 if ($user) return $user;
146 }
147 }
148 }
149 return FALSE;
150 }
151 }
152 ?>