| 1 | <?php /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 2; -*- |
|---|
| 2 | |
|---|
| 3 | */ |
|---|
| 4 | |
|---|
| 5 | require_once 'class.dkg.site.php'; |
|---|
| 6 | |
|---|
| 7 | if (!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."\"><".$x->_email."></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 | |
|---|
| 328 | global $jpdd; |
|---|
| 329 | |
|---|
| 330 | ?> |
|---|