source: trunk/jpdd/class.jpdd.php @ 151

Last change on this file since 151 was 151, checked in by dkg, 6 years ago

JPDD: added overview which shows who is still "unaffiliated"

File size: 15.9 KB
Line 
1<?php  /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 2; -*-
2
3           */
4
5require_once 'class.dkg.site.php';
6
7if (!class_exists('JPDD_JPDD')) {
8
9  class JPDD_JPDD extends DKG_Site {
10
11    function JPDD_JPDD() {
12      $this->DKG_Site();
13      $this->addClassMapEntry('workshop', array('classname' => 'JPDD_Workshop', 
14                                                'filename' => 'class.jpdd.workshop.php',
15                                                'table' => 'workshop',
16                                                'sort' => 'title'));
17      // overrides the default DKG_Person map, which is fine because
18      // it's a subclass:
19      $this->addClassMapEntry('person', array('classname' => 'JPDD_Person', 
20                                              'filename' => 'class.jpdd.person.php',
21                                              'table' => 'person',
22                                              'sort' => 'first_name,last_name'));
23      $this->addClassMapEntry('category', array('classname' => 'JPDD_Category', 
24                                                'filename' => 'class.jpdd.category.php',
25                                                'table' => 'category',
26                                                'sort' => 'title'));
27      $this->addClassMapEntry('room', array('classname' => 'JPDD_Room', 
28                                                'filename' => 'class.jpdd.room.php',
29                                                'table' => 'room',
30                                                'sort' => 'title'));
31      $this->addClassMapEntry('organization', array('classname' => 'JPDD_Organization', 
32                                                    'filename' => 'class.jpdd.organization.php',
33                                                    'table' => 'organization',
34                                                    'sort' => 'title'));
35    }
36
37
38    function getDefaultPage() {
39      return $this->getSnippet('home');
40    }
41
42    function initialize() {
43      parent::initialize();
44      $this->_stylesheets[] = 'jpdd.css';
45    }
46
47    function getNavLink($title,$targ) {
48      return '<a '.(($this->_action == $targ || (in_array($this->_action, array('view', 'edit')) && $this->_type == $targ)) ? 'class="current" ' : '').'href='.$this->Path($targ).'>'.$title.'</a>';
49    }
50
51    function getNavLinks() {
52      // FIXME: this is way way way too static.
53      $links = array('Categories' => 'category',
54                     'Participating Schools' => 'organization');
55
56      if ($this->isAuthenticated()) {
57        if ($this->_authenticated_user->hasAnyOfThesePrivileges('Update Workshops'))
58          $links['Workshops'] = 'workshop';
59        if ($this->_authenticated_user->hasAnyOfThesePrivileges('List People'))
60          $links['People'] = 'person';
61        if ($this->_authenticated_user->hasAnyOfThesePrivileges('List Rooms'))
62          $links['Rooms'] = 'room';
63        if ($this->_authenticated_user->hasAllOfThesePrivileges('Update Workshops', 'Edit People', 'Edit Categories'))
64          $links['Review Signups'] = 'overview';
65      } else {
66        if ($this->_action != 'newacct')
67          $links['Create New Account'] = 'newacct';
68      }
69      return '<div class="navlinks">'."\n".
70        join(' ', array_map(create_function('$t,$v', 'global $jpdd; return $jpdd->getNavLink($t,$v);'), array_keys($links), $links)).
71        "\n".'</div>';
72    }
73
74    function getTopofPage() {
75      return '<a href="'.$this->Path().'"><div class="header"><h1>'.$this->getPageTitle().'</h1>'.
76        '<div class="subtitle"><q>'.$this->_site_subtitle.'</q></div>'.
77        '<div class="date">'.$this->_site_date.'</div>'.
78        '</div></a>'."\n";
79    } 
80    function getSidebar() {
81      return '<div class="sidebar">
82'.(in_array($this->_action, array('login', 'logout', 'resetpass', 'newacct')) ? '' : $this->getLoginForm()).'
83'.$this->getNavLinks().'
84</div>';
85    }
86
87    function getPageBody() {
88      return $this->getTopofPage().'<div class="maincontent">'.$this->getMainContent().$this->getSnippet('directions').'</div>'.$this->getSidebar().$this->getSnippet('footer');
89    }
90
91
92    function getWorkshops() {
93      $workshops = $this->getAll('workshop');
94     
95      $ret = '<dl>';
96      reset($workshops);
97      while (list(,$workshop) = each($workshops)) {
98        $ret .= '<dt><a href="'.$this->Path('workshop', (int)($workshop->_id)).'">'.htmlentities($workshop->_title).'</a></dt>'.
99          '<dd>'.htmlentities($workshop->_description).'</dd>';
100      }
101      return $ret.'</ul>';
102    }
103
104
105    function getAdditionalSalutation() {
106      $x = $this->_authenticated_user->getSalutation();
107      if ('' != $x)
108        return '<div>'.$x.'</div>';
109      return $x;
110    }
111
112    function getAllowedActions() {
113      return array_merge(parent::getAllowedActions(), array('signup', 'printout', 'overview'));
114    }
115
116    function getGenericOverview() {
117      return '<ul>'."\n".
118        '<li>Workshops available: '.$this->getValueFromSQL('SELECT COUNT(*) AS foo FROM workshop', 'foo')."\n".
119        '<li>Workshop presenters: '.(int)$this->getValueFromSQL('SELECT COUNT(*) AS foo FROM presenter', 'foo')."\n".
120        '<li>Workshop attendees: '.(int)$this->getValueFromSQL('SELECT COUNT(*) AS foo FROM audience', 'foo')."\n".
121        '<li>Complete on-line accounts: '.$this->getValueFromSQL('SELECT COUNT(*) AS foo FROM person WHERE pass IS NOT NULL', 'foo')."\n".
122        '<ul><li>who aren\'t presenters or attendees yet: '.(int)$this->getValueFromSQL('SELECT COUNT(*) AS foo FROM (SELECT id FROM person WHERE pass IS NOT NULL) AS p LEFT JOIN attendance ON (p.id = attendance.person_id) WHERE workshop_id IS NULL', 'foo')."\n".
123        '<li>who are <a href="'.$this->Path('overview', 'unaffiliated').'">unaffiliated with any school</a>: '.(int)$this->getValueFromSQL('SELECT COUNT(*) AS foo FROM (SELECT id FROM person WHERE pass IS NOT NULL) AS p LEFT JOIN affiliation ON (p.id = affiliation.person_id) WHERE organization_id IS NULL', 'foo')."</ul>\n".
124        '<li>People without e-mail addresses: '.(int)$this->getValueFromSQL('SELECT COUNT(*) AS foo FROM person WHERE email IS NULL', 'foo')."\n".
125        '</ul>';
126    }
127
128    function getUnaffiliatedAccounts() {
129      $sql = 'SELECT person.* FROM person LEFT JOIN affiliation ON (person.id = affiliation.person_id) WHERE affiliation.id IS NULL AND person.pass IS NOT NULL';
130      $us = $this->getSeriesFromSQL($sql, 'JPDD_Person');
131      return "<h3>Unaffiliated accounts<ul>\n".join('', array_map(create_function('$x', 'return "<li>".$x->getLinkedTitle().(is_null($x->_email) ? "" : " - <a class=\"email\" href=\"mailto:".$x->_email."\">&lt;".$x->_email."&gt;</a>")."\n";'), $us))."</ul>";
132    }
133
134    function getSchoolOverview() {
135      $sql = 'SELECT MAX(organization.title) AS title, organization.id, COUNT(*) AS registrations FROM audience LEFT JOIN affiliation ON (audience.person_id = affiliation.person_id) LEFT JOIN organization ON (affiliation.organization_id = organization.id) GROUP BY organization.id ORDER BY MAX(organization.title)';
136      $os = $this->getSeriesFromSQL($sql);
137      return '<table><tr><th>school</th><th>signups</th></tr>'."\n<tbody>\n".
138        join('', array_map(create_function('$o', 'global $jpdd; return "<tr><td>".($jpdd->isEmpty($o["title"]) ? "unaffiliated" : "<a href=\"".$jpdd->Path("organization", $o["id"])."\">".htmlentities($o["title"])."</a>")."</td><td class=\"attendance\">".(int)$o["registrations"]."</td></tr>\n";'), $os)).
139        '</tbody></table>'."\n";
140    }
141
142    function getWorkshopOverviewWithAttendence() {
143      return '<table><tr><th>workshop</th><th>max</th><th>min</th><th>total</th><th>by school</th></tr>'."\n<tbody>\n".
144        join('', array_map(create_function('$w', 'return $w->getOverviewRowWithAttendence();'), $this->getAll('workshop'))).
145        '</tbody></table>'."\n";
146    }
147
148    function handleSignup() {
149      if (($this->_type) != 'workshop') {
150        $this->addWarning('You can only sign up for a workshop.');
151        return '';
152      }
153      $workshop = $this->getItem('workshop', $this->_identifier);
154     
155      // must POST
156      if ($_SERVER['REQUEST_METHOD'] != 'POST') {
157        $x = $this->Path('workshop', (int)$workshop->getID());
158        $this->redirectLocal($x);
159        exit;
160      }
161      if (!$this->isAuthenticated()) {
162        // FIXME: should this be $this->permissionDenied() instead?  if not, document why not.
163        $this->addWarning('You must be logged in to sign up for a workshop.');
164        return $workshop->getDetailView();
165      }
166      // must match form token
167      if (!$this->verifyFormToken()) {
168        $this->addWarning('Form token did not match.');
169        return $workshop->getDetailView();
170      }
171
172      if ($_POST['cancel'] == 'cancel') {
173        // this is to cancel a signup.
174        if (!$this->_authenticated_user->isAttendingWorkshop($workshop->getID(), 'audience')) {
175          $this->addWarning('You are not signed up for this workshop at the moment.');
176          return $workshop->getDetailView();
177        }
178        $sql = 'DELETE FROM attendance WHERE workshop_id = '.(int)$workshop->getID().' AND person_id = '.(int)$this->_authenticated_user->getID().' AND flavor = '.$this->escStr('audience');
179        $this->executeSQL($sql);
180      } else {
181        // this is to actually sign up for this course.
182        // authenticated user must not be attending any workshop currently.
183        if ($this->_authenticated_user->getAttendanceCount() > 0) {
184          $this->addWarning('You are already signed up for a workshop.');
185          return $workshop->getDetailView();
186        }
187        // must be room in the workshop.
188        if ($workshop->isFull()) {
189          $this->addWarning('Sorry, '.$workshop->getLinkedTitle().' is already full.  Please choose <a href="'.$this->Path('category').'">another workshop</a>.');
190          return $workshop->getDetailView();
191        }
192        $sql = 'INSERT INTO attendance (workshop_id, person_id, flavor) VALUES ('.(int)$workshop->getID().', '.(int)$this->_authenticated_user->getID().', '.$this->escStr('audience').')';
193        $this->executeSQL($sql);
194      }
195      return $workshop->getDetailView();
196    }
197
198    function getMainContentExtended() {
199      if ($this->_action == 'signup') {
200        return $this->handleSignup();
201      } elseif ($this->_action == 'printout') {
202        $pdf = NULL;
203        $pdfname = array_shift($this->_extra_URI_args);
204        if ($this->_type == 'person') {
205          if ($this->isAuthenticated()) {
206            $subj = $this->getItem('person', (int)$this->_identifier);
207            if ($this->_authenticated_user->canEdit($subj) || ($this->_authenticated_user->getID() == $subj->getID())) {
208              // those who can edit the person are allowed to view their pdfs also.
209              $pdf = $subj->getPDF($pdfname);
210            }
211          } else {
212            $this->permissionDenied('You must be logged in to view an individual printout.');
213          }
214        }
215        if (!is_null($pdf)) {
216          // we've got a $pdf, we should emit it properly.
217          $pdf->Output($pdfname, 'I');
218        }
219      } elseif ($this->_action = 'overview') {
220        // we're using the presence of all three privileges as a proxy for sysadmin status:
221        if ($this->isAuthenticated() && $this->_authenticated_user->hasAllOfThesePrivileges('Update Workshops', 'Edit Categories', 'Edit People')) {
222          return $this->getOverview($this->_type);
223        } else {
224          $this->permissionDenied('You are not authorized to view this overview.');
225        }
226      }
227      return parent::getMainContentExtended();
228    }
229
230    function getOverview($which) {
231      global $jpdd;
232      if (is_null($which))
233        $which = '';
234
235      $ret = '<div class="overviewnav">';
236
237      // choice of administrator overviews:
238      $overviews = array('Overview' => '',
239                         'By Workshop' => 'workshop',
240                         'By School' => 'organization');
241     
242      reset($overviews);
243      while (list($t,$v) = each($overviews)) {
244        $ret .= '<a '.($which == $v ? 'class="current" ' : '').'href="'.$this->Path('overview', $v).'">'.$t.'</a> ';
245      }
246      $ret .= '</div>';
247
248      if ($which == 'workshop') {
249        $ret .= $this->getWorkshopOverviewWithAttendence();
250      } elseif ($which == 'organization') {
251        $ret .= $this->getSchoolOverview();
252      } elseif ($which == 'unaffiliated') {
253        $ret .= $this->getUnaffiliatedAccounts();
254      } else {
255        $ret .= $this->getGenericOverview();
256      }
257      return $ret;
258    }
259   
260
261    // new account requirements:
262    // FIXME: these should somehow call on JPDD_Person directly, instead of being here...
263   
264    function getNewAccountFormExtraFields() {
265      $lasterr = (($_SERVER['REQUEST_METHOD'] == 'POST') && $this->isEmpty($_POST['last_name']));
266      $orgs = $this->getAll('organization');
267      //      $this->addSourcedScript('scripts/dkg.base.js');
268      //      $this->addScriptChunk('DKG.onLoadScripts.push(\'DKG.ShowOnValue("", "other", "org_title");\');');
269      $acct = $this->getItem('person', (int)$this->_identifier);
270     
271      return '<label>First name:<br/><input type="text" name="first_name" value="'.htmlentities($this->defaultOnEmpty($_POST['first_name'], $acct->_first_name)).'"/></label><br/>
272'.//<label>Middle name:<br/><input type="text" name="middle_name" value="'.htmlentities($_POST['middle_name']).'"/></label><br/>
273'<label>'.$this->getRequiredFieldLabel().'Last name:<br/>'.
274        ($lasterr ? '<span class="error">Must include a last name</span><br/>' : '').
275'<input type="text" '.($lasterr ? 'class="error" ' : '').'name="last_name" value="'.htmlentities($this->defaultOnEmpty($_POST['last_name'], $acct->_last_name)).'"/></label><br/>
276
277'.$acct->getM2MEditView('organization', 'affiliation', 'Affiliated With', 'Add Affiliation')
278
279        /*
280.'<fieldset><legend>Affiliated With</legend>
281<label><select onchange="DKG.ShowOnValue(this.value, \'other\', \'org_title\');" " name="affiliate_id">
282<option value=""></option>
283'.join("\n",array_map(create_function('$n', 'return "<option value=\"".$n->getID()."\">".htmlentities($n->getTitle())."</option>";'),$orgs)).'
284<option value="other">Other...</option>
285</select></label><br/>
286<label id="org_title">School Name:<br/><input name="org_title" type="text" value="'.htmlentities($_POST['org_title']).'"/></label>
287</fieldset>
288'
289        */
290;
291    }
292    // return an array of update statements:
293    function handleNewAccountFormExtraFields() {
294      return array('first_name = '.$this->stringOrDefault($_POST['first_name']),
295                   'middle_name = '.$this->stringOrDefault($_POST['middle_name']),
296                   'last_name = '.$this->stringOrDefault($_POST['last_name']));
297    }
298    // return true if extra fields are all properly validated and can
299    // continue:
300    function validateNewAccountFormExtraFields() {
301      return (!$this->isEmpty($_POST['last_name']));
302    }
303
304    function handleAdditionalNewAccountUpdates($id) {
305      $affiliate = $_POST['affiliate_id'];
306      if ('other' == $affiliate) {
307        if ($this->isEmpty($_POST['org_title'])) {
308          $affiliate = 0;
309        } else {
310          // try to make a new org:
311          require_once('class.jpdd.organization.php');
312          $org = new JPDD_Organization(array());
313          $org->handleCreation('org_');
314          $affiliate = $org->getID();
315        }
316      } else {
317        $rem = array_key_exists('remove_affiliation', $_POST) ? $_POST['remove_affiliation'] : array();
318        if (!is_array($rem)) $rem = array($rem);
319        $add = array_key_exists('add_affiliation', $_POST) ? $_POST['add_affiliation'] : array();
320        if (!is_array($add)) $add = array($add);
321        array_map(create_function('$x', 'global $jpdd; $jpdd->executeSQL("INSERT INTO affiliation (person_id, organization_id) VALUES ('.(int)$id.', ".(int)$x.")");'), array_filter($add, create_function('$x', 'global $jpdd; return !$jpdd->isEmpty($x);')));
322        array_map(create_function('$x', 'global $jpdd; $jpdd->executeSQL("DELETE FROM affiliation WHERE person_id = '.(int)$id.' AND organization_id = ".(int)$x);'), array_filter($rem, create_function('$x', 'global $jpdd; return !$jpdd->isEmpty($x);')));
323      }
324    }
325  }
326}
327
328global $jpdd;
329
330?>
Note: See TracBrowser for help on using the repository browser.