PageRenderTime 42ms CodeModel.GetById 11ms app.highlight 25ms RepoModel.GetById 2ms app.codeStats 0ms

/lib/orchestra_register.php

https://github.com/timhunt/Orchestra-register
PHP | 695 lines | 537 code | 103 blank | 55 comment | 66 complexity | b617bd667a77ca46a1e86b5a496b8ff1 MD5 | raw file
  1<?php
  2
  3// Orchestra Register is free software: you can redistribute it and/or modify
  4// it under the terms of the GNU General Public License as published by
  5// the Free Software Foundation, either version 3 of the License, or
  6// (at your option) any later version.
  7//
  8// Orchestra Register is distributed in the hope that it will be useful,
  9// but WITHOUT ANY WARRANTY; without even the implied warranty of
 10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 11// GNU General Public License for more details.
 12//
 13// You should have received a copy of the GNU General Public License
 14// along with Orchestra Register. If not, see <http://www.gnu.org/licenses/>.
 15
 16
 17/**
 18 * System class. Provides a facade to all the functionality.
 19 *
 20 * @package orchestraregister
 21 * @copyright 2009 onwards Tim Hunt. T.J.Hunt@open.ac.uk
 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 23 */
 24class orchestra_register {
 25    private $user;
 26    private $players = null;
 27    private $events = null;
 28    private $parts = null;
 29    private $sections = null;
 30    private $request;
 31    private $output = null;
 32    /** @var sys_config */
 33    private $sysconfig;
 34    /** @var db_config */
 35    private $config;
 36    private $version;
 37    private $db;
 38    private $attendanceloaded = false;
 39    private $seriesid;
 40
 41    public function __construct($requireinstalled = true) {
 42
 43        if ($requireinstalled && !is_readable(dirname(__FILE__) . '/../config.php')) {
 44            $this->redirect('install.php', false, 'none');
 45        }
 46
 47        $config = new sys_config();
 48        include(dirname(__FILE__) . '/../config.php');
 49        $config->check();
 50        $this->sysconfig = $config;
 51
 52        $version = new version();
 53        include(dirname(__FILE__) . '/../version.php');
 54        $this->version = $version;
 55
 56        $this->request = new request();
 57        $this->output = new html_output($this);
 58        session_start();
 59
 60        $this->db = new database($this->sysconfig->dbhost, $this->sysconfig->dbuser,
 61                $this->sysconfig->dbpass, $this->sysconfig->dbname);
 62
 63        $this->config = $this->db->load_config();
 64        if (!$this->config) {
 65            if ($requireinstalled) {
 66                $this->redirect('install.php', false, 'none');
 67            } else {
 68                return;
 69            }
 70        }
 71        $this->config = $this->db->check_installed($this->config,
 72                $this->version->id, $this->sysconfig->pwsalt);
 73
 74        set_exception_handler(array($this->output, 'exception'));
 75
 76        date_default_timezone_set($this->config->timezone);
 77
 78        $this->seriesid = $this->request->get_param('s', request::TYPE_INT, null, false);
 79        if (is_null($this->seriesid) || !$this->check_series_exists($this->seriesid)) {
 80            $this->seriesid = $this->config->defaultseriesid;
 81        }
 82    }
 83
 84    public function set_series_id($seriesid) {
 85        if (!$this->check_series_exists($seriesid)) {
 86            throw new not_found_exception('Unknown series.', $seriesid);
 87        }
 88        if ($this->seriesid != $seriesid) {
 89            $this->seriesid = $seriesid;
 90            $this->players = null;
 91            $this->events = null;
 92            $this->attendanceloaded = false;
 93        }
 94    }
 95
 96    public function install() {
 97        $this->db->install($this->version->id);
 98    }
 99
100    public function get_request() {
101        return $this->request;
102    }
103
104    /**
105     * @return html_output
106     */
107    public function get_output() {
108        return $this->output;
109    }
110
111    /**
112     * @return mail_helper
113     */
114    public function emails() {
115        return new mail_helper($this);
116    }
117
118    public function get_players($includenotplaying = false, $currentuserid = null) {
119        if (is_null($this->players)) {
120            $this->players = $this->db->load_players($this->seriesid,
121                    $includenotplaying, $currentuserid);
122        }
123        return $this->players;
124    }
125
126    public function get_user($userid, $includedisabled = false) {
127        return $this->db->find_user_by_id($userid, $includedisabled);
128    }
129
130    public function get_users($includedisabled = false) {
131        return $this->db->load_users($includedisabled);
132    }
133
134    /**
135     * Get the data about an event.
136     * @param int $id
137     * @param bool $includedeleted
138     * @return event
139     */
140    public function get_event($id, $includedeleted = false) {
141        $event = $this->db->find_event_by_id($id, $includedeleted);
142        if (!$event) {
143            throw new not_found_exception('Unknown event.', $id);
144        }
145        return $event;
146    }
147
148    public function get_events($includepast = false, $includedeleted = false) {
149        if (is_null($this->events)) {
150            $this->events = $this->db->load_events($this->seriesid, $includepast, $includedeleted);
151        }
152        return $this->events;
153    }
154
155    public function get_previous_event($eventid) {
156        $previousevent = null;
157        foreach ($this->get_events(true) as $event) {
158            if ($event->id == $eventid) {
159                return $previousevent;
160            }
161            $previousevent = $event;
162        }
163        return null;
164    }
165
166    public function get_next_event($eventid) {
167        $found = false;
168        foreach ($this->get_events(true) as $event) {
169            if ($found) {
170                return $event;
171            }
172            if ($event->id == $eventid) {
173                $found = true;
174            }
175        }
176        return null;
177    }
178
179    public function get_parts($includenotplaying = false) {
180        if (is_null($this->parts)) {
181            $partsdata = $this->db->load_parts();
182            $this->parts = array();
183            foreach ($partsdata as $part) {
184                $this->parts[$part->section][$part->part] = $part->part;
185            }
186            if ($includenotplaying) {
187                $this->parts['Not playing'][0] = 'Not playing';
188            }
189        }
190        return $this->parts;
191    }
192
193    /**
194     * @return array nested structure represending all the secions and parts.
195     */
196    public function get_sections_and_parts() {
197        if (is_null($this->sections)) {
198            $partsdata = $this->db->load_sections_and_parts();
199            $this->sections = array();
200            $currentsection = null;
201            foreach ($partsdata as $part) {
202                if ($part->section != $currentsection) {
203                    $currentsection = $part->section;
204                    $this->sections[$currentsection] = new stdClass();
205                    $this->sections[$currentsection]->section = $part->section;
206                    $this->sections[$currentsection]->sectionsort = $part->sectionsort;
207                    $this->sections[$currentsection]->parts = array();
208                }
209                if (!is_null($part->part)) {
210                    $this->sections[$currentsection]->parts[$part->part] = new stdClass();
211                    $this->sections[$currentsection]->parts[$part->part]->part = $part->part;
212                    $this->sections[$currentsection]->parts[$part->part]->section = $currentsection;
213                    $this->sections[$currentsection]->parts[$part->part]->partsort = $part->partsort;
214                    $this->sections[$currentsection]->parts[$part->part]->inuse = $part->inuse;
215                }
216            }
217        }
218        return $this->sections;
219    }
220
221    public function get_part_data($part) {
222        foreach ($this->get_sections_and_parts() as $sectiondata) {
223            if (array_key_exists($part, $sectiondata->parts)) {
224                return $sectiondata->parts[$part];
225            }
226        }
227        return null;
228    }
229
230    function is_valid_part($part) {
231        foreach ($this->get_parts(true) as $sectionparts) {
232            if (isset($sectionparts[$part])) {
233                return true;
234            }
235        }
236        return false;
237    }
238
239    function get_section($part) {
240        foreach ($this->get_parts(true) as $section => $sectionparts) {
241            if (isset($sectionparts[$part])) {
242                return $section;
243            }
244        }
245        throw new coding_error('Unknown part.');
246    }
247
248    function is_valid_section($section) {
249        return array_key_exists($section, $this->get_sections_and_parts());
250    }
251
252    public function create_part($section, $part) {
253        $this->db->insert_part($section, $part);
254    }
255
256    public function create_section($section) {
257        $this->db->insert_section($section);
258    }
259
260    public function rename_part($oldname, $newname) {
261        $this->db->rename_part($oldname, $newname);
262    }
263
264    public function rename_section($oldname, $newname) {
265        $this->db->rename_section($oldname, $newname);
266    }
267
268    public function delete_part($part) {
269        $this->db->delete_part($part);
270    }
271
272    public function delete_section($section) {
273        $this->db->delete_section($section);
274    }
275
276    public function swap_section_order($section1, $section2) {
277        $sections = $this->get_sections_and_parts();
278        $this->db->swap_section_order($section1, $sections[$section1]->sectionsort,
279                $section2, $sections[$section2]->sectionsort);
280    }
281
282    public function swap_part_order($part1, $part2) {
283        $sections = $this->get_sections_and_parts();
284        $this->db->swap_part_order($part1, $this->get_part_data($part1)->partsort,
285                $part2, $this->get_part_data($part2)->partsort);
286    }
287
288    public function get_series($id, $includedeleted = false) {
289        $series = $this->db->find_series_by_id($id, $includedeleted);
290        if (!$series) {
291            throw new not_found_exception('Unknown series.', $id);
292        }
293        return $series;
294    }
295
296    public function get_series_list($includedeleted = false) {
297        return $this->db->load_series($includedeleted);
298    }
299
300    public function get_series_options() {
301        $series = $this->db->load_series();
302        $options = array();
303        foreach ($series as $s) {
304            $options[$s->id] = $s->name;
305        }
306        return $options;
307    }
308
309    public function load_attendance() {
310        if ($this->attendanceloaded) {
311            return;
312        }
313        $attendances = $this->db->load_attendances($this->seriesid);
314        foreach ($attendances as $a) {
315            $this->players[$a->userid]->attendance[$a->eventid] = $a;
316        }
317        $this->attendanceloaded = true;
318    }
319
320    public function load_subtotals() {
321        $data = $this->db->load_subtotals($this->seriesid);
322        $subtotals = array();
323        foreach ($data as $row) {
324            if (!array_key_exists($row->part, $subtotals)) {
325                $subtotal = new stdClass;
326                $subtotal->section = $row->section;
327                $subtotal->attending = array();
328                $subtotal->numplayers = array();
329                $subtotals[$row->part] = $subtotal;
330            }
331            $subtotals[$row->part]->attending[$row->eventid] = $row->attending;
332            $subtotals[$row->part]->numplayers[$row->eventid] = $row->numplayers;
333        }
334        return $subtotals;
335    }
336
337    public function get_subtotals($events) {
338        $subtotals = $this->load_subtotals();
339        $totalplayers = array();
340        $totalattending = array();
341        $sectionplayers = array();
342        $sectionattending = array();
343        foreach ($events as $event) {
344            $totalplayers[$event->id] = 0;
345            $totalattending[$event->id] = 0;
346            foreach ($this->get_sections_and_parts() as $section => $notused) {
347                $sectionplayers[$section][$event->id] = 0;
348                $sectionattending[$section][$event->id] = 0;
349            }
350
351            foreach ($subtotals as $part => $subtotal) {
352                if ($subtotal->numplayers[$event->id]) {
353                    $totalplayers[$event->id] += $subtotal->numplayers[$event->id];
354                    $totalattending[$event->id] += $subtotal->attending[$event->id];
355
356                    if (!isset($sectionplayers[$subtotal->section][$event->id])) {
357                        $sectionplayers[$subtotal->section][$event->id] = 0;
358                        $sectionattending[$subtotal->section][$event->id] = 0;
359                    }
360
361                    $sectionplayers[$subtotal->section][$event->id] += $subtotal->numplayers[$event->id];
362                    $sectionattending[$subtotal->section][$event->id] += $subtotal->attending[$event->id];
363                }
364            }
365        }
366        return array($subtotals, $totalplayers, $totalattending, $sectionplayers, $sectionattending);
367    }
368
369    public function load_selected_players($parts, $eventid, $statuses) {
370        return $this->db->load_selected_players($this->seriesid, $parts, $eventid, $statuses);
371    }
372
373    public function set_player_part($player, $newpart, $seriesid = null) {
374        if (is_null($seriesid)) {
375            $seriesid = $this->seriesid;
376        }
377        $this->db->set_player_part($player->id, $seriesid, $newpart);
378    }
379
380    public function get_player_parts($user) {
381        return $this->db->load_player_parts($user->id);
382    }
383
384    public function copy_players_between_series($oldseriesid, $newseriesid) {
385        $this->db->copy_players_between_series($oldseriesid, $newseriesid);
386    }
387
388    public function set_attendance($player, $event, $newattendance) {
389        $this->db->set_attendance($player->id, $this->seriesid, $event->id, $newattendance);
390    }
391
392    public function create_user($user) {
393        $this->db->insert_user($user);
394    }
395
396    public function update_user($user) {
397        $this->db->update_user($user);
398    }
399
400    public function set_user_password($userid, $newpassword) {
401        $this->db->set_password($userid, $this->sysconfig->pwsalt . $newpassword);
402    }
403
404    public function create_event($event) {
405        $this->db->insert_event($event);
406    }
407
408    public function update_event($event) {
409        $this->db->update_event($event);
410    }
411
412    public function delete_event($event) {
413        $this->db->set_event_deleted($event->id, 1);
414    }
415
416    public function undelete_event($event) {
417        $this->db->set_event_deleted($event->id, 0);
418    }
419
420    public function create_series($series) {
421        $this->db->insert_series($series);
422    }
423
424    public function update_series($series) {
425        $this->db->update_series($series);
426    }
427
428    public function delete_series($series) {
429        $this->db->set_series_deleted($series->id, 1);
430    }
431
432    public function undelete_series($series) {
433        $this->db->set_series_deleted($series->id, 0);
434    }
435
436    public function get_login_info() {
437        $this->get_current_user();
438        if ($this->user->is_logged_in()) {
439            return 'You are logged in as ' . $this->user->get_name() .
440                    '. <a href="' . $this->url('logout.php', false) . '">Log out</a>.';
441        } else {
442            return 'You are not logged in. <a href="' . $this->url('login.php') . '">Log in</a>.';
443        }
444    }
445
446    public function get_event_guid($event) {
447        return 'event' . $event->id . '@' . $this->config->icalguid;
448    }
449
450    public function url($relativeurl, $withtoken = true, $xmlescape = true, $seriesid = null) {
451        $extra = array();
452
453        if (is_null($seriesid)) {
454            $seriesid = $this->seriesid;
455        }
456        if ($seriesid != 'none' && $seriesid != $this->config->defaultseriesid) {
457            $extra[] = 's=' . $seriesid;
458        }
459
460        if ($withtoken && empty($_SESSION['userid']) && !empty($this->user->authkey)) {
461            $extra[] = 't=' . $this->user->authkey;
462        }
463
464        $extra = implode('&', $extra);
465        if ($extra) {
466            if (strpos($relativeurl, '?') !== false) {
467                $extra = '&' . $extra;
468            } else {
469                $extra = '?' . $extra;
470            }
471        }
472
473        $url = $this->sysconfig->wwwroot . $relativeurl . $extra;
474        if ($xmlescape) {
475            return htmlspecialchars($url);
476        } else {
477            return $url;
478        }
479    }
480
481    public function get_param($name, $type, $default = null, $postonly = true) {
482        return $this->request->get_param($name, $type, $default, $postonly);
483    }
484
485    public function get_array_param($name, $type, $default = null, $postonly = true) {
486        return $this->request->get_array_param($name, $type, $default, $postonly);
487    }
488
489    public function require_sesskey() {
490        if ($this->get_sesskey() != $this->get_param('sesskey', request::TYPE_AUTHTOKEN)) {
491            throw new forbidden_operation_exception(
492                    'The request you just made could not be verified. ' .
493                    'Please click back, reload the page, and try again. ' .
494                    '(The session-key did not match.)');
495        }
496    }
497
498    public function refresh_sesskey() {
499        $this->get_current_user()->refresh_sesskey();
500    }
501
502    public function get_sesskey() {
503        return $this->get_current_user()->sesskey;
504    }
505
506    public function verify_login($email, $password) {
507        $this->require_sesskey();
508        $user = $this->db->check_user_auth($email, $this->sysconfig->pwsalt . $password);
509        if ($user) {
510            session_regenerate_id(true);
511            if ($this->config->changesesskeyonloginout) {
512                $this->refresh_sesskey();
513            }
514            $_SESSION['userid'] = $user->id;
515            $this->user = $user;
516            $this->user->authlevel = user::AUTH_LOGIN;
517            return true;
518        }
519        return false;
520    }
521
522    public function logout() {
523        unset($_SESSION['userid']);
524        session_regenerate_id(true);
525        if ($this->config->changesesskeyonloginout) {
526            $this->refresh_sesskey();
527        }
528    }
529
530    public function check_series_exists($seriesid) {
531        return $this->db->find_series_by_id($seriesid);
532    }
533
534    public function get_current_seriesid() {
535        return $this->seriesid;
536    }
537
538    /** @return user */
539    public function get_current_user() {
540        if (!$this->user) {
541            $this->user = $this->find_current_user();
542            $this->user->maintenancemode = $this->is_in_maintenance_mode();
543        }
544        return $this->user;
545    }
546
547    /**
548     * Helper used by {@link get_current_user()}.
549     * @return user
550     */
551    protected function find_current_user() {
552        if (!empty($_SESSION['userid'])) {
553            $user = $this->db->find_user_by_id($_SESSION['userid']);
554            if ($user) {
555                $user->authlevel = user::AUTH_LOGIN;
556                return $user;
557            }
558        }
559
560        $token = $this->request->get_param('t', request::TYPE_AUTHTOKEN, null, false);
561        if ($token) {
562            $user = $this->db->find_user_by_token($token);
563            if ($user) {
564                $user->authlevel = user::AUTH_TOKEN;
565                return $user;
566            }
567        }
568
569        return new user();
570    }
571
572    public function redirect($relativeurl, $withtoken = true, $seriesid = null) {
573        header('HTTP/1.1 303 See Other');
574        header('Location: ' . $this->url($relativeurl, $withtoken, false, $seriesid));
575        exit(0);
576    }
577
578    /**
579     * @return db_config
580     */
581    public function get_config() {
582        return $this->config;
583    }
584
585    public function set_config($name, $value) {
586        if (!$this->config->is_settable_property($name)) {
587            throw new coding_error('Cannot set that configuration variable.',
588                    'Name: ' . $name . ', Value: ' . $value);
589        }
590        $this->db->set_config($name, $value, $this->config);
591    }
592
593    /**
594     * For use by install.php only.
595     */
596    public function set_default_config() {
597        $this->config = new db_config();
598        $this->user = new user();
599    }
600
601    public function version_string() {
602        return 'Orchestra Register ' . $this->version->name . ' (' . $this->version->id . ')';
603    }
604
605    public function get_title() {
606        return $this->config->title;
607    }
608
609    public function get_motd_heading() {
610        return $this->config->motdheading;
611    }
612
613    public function get_motd() {
614        return $this->config->motd;
615    }
616
617    /**
618     * Get the lists of actions that appear under the register.
619     * @param user $user
620     * @param bool $includepast whether past events are currently included.
621     * @param string|bool $printurl whether to include a print view link. String script name to include. false to exclude.
622     * @param string|bool $showhideurl whether to include the show/hide events in the past URL.
623     * @return array with two elements, both actions objects.
624     */
625    public function get_actions_menus($user, $includepast, $printurl = '', $showhideurl = '') {
626        if ($includepast) {
627            $showhidepasturl = $this->url($showhideurl);
628            $showhidepastlabel = 'Hide events in the past';
629            $fullprinturl = $this->url('?print=1&past=1');
630        } else {
631            if (strpos($showhideurl, '?') !== false) {
632                $join = '&';
633            } else {
634                $join = '?';
635            }
636            $showhidepasturl = $this->url($showhideurl . $join . 'past=1');
637            $showhidepastlabel = 'Show events in the past';
638            $fullprinturl = $this->url($printurl . '?print=1');
639        }
640
641        $seriesactions = new actions();
642        if (is_string($showhideurl)) {
643            $seriesactions->add($showhidepasturl, $showhidepastlabel);
644        }
645        if (is_string($printurl)) {
646            $seriesactions->add($fullprinturl, 'Printable view');
647        }
648        $seriesactions->add($this->url('ical.php', false), 'Download iCal file (to add the rehearsals into Outlook, etc.)');
649        $seriesactions->add($this->url('wikiformat.php'), 'List of events to copy-and-paste into the wiki', $user->can_edit_events());
650        $seriesactions->add($this->url('players.php'), 'Edit the list of players', $user->can_edit_players());
651        $seriesactions->add($this->url('events.php'), 'Edit the list of events', $user->can_edit_events());
652        $seriesactions->add($this->url('extractemails.php'), 'Get a list of email addresses', $user->can_edit_users());
653
654        $systemactions = new actions();
655        $systemactions->add($this->url('users.php'), 'Edit the list of users', $user->can_edit_users());
656        $systemactions->add($this->url('series.php'), 'Edit the list of rehearsal series', $user->can_edit_series());
657        $systemactions->add($this->url('parts.php'), 'Edit the available sections and parts', $user->can_edit_parts());
658        $systemactions->add($this->url('editmotd.php'), 'Edit introductory message', $user->can_edit_motd());
659        $systemactions->add($this->url('admin.php'), 'Edit the system configuration', $user->can_edit_config());
660        $systemactions->add($this->url('logs.php'), 'View the system logs', $user->can_view_logs());
661
662        return array($seriesactions, $systemactions);
663    }
664
665    public function get_help_url() {
666        return $this->config->helpurl;
667    }
668
669    public function get_wiki_edit_url() {
670        return $this->config->wikiediturl;
671    }
672
673    public function is_in_maintenance_mode() {
674        return $this->get_config()->maintenancemode;
675    }
676
677    public function log($action) {
678        if (empty($this->user->id)) {
679            throw new coding_error('Cannot log an un-authenicated action.');
680        }
681        $this->db->insert_log($this->user->id, $this->user->authlevel, $action);
682    }
683
684    public function log_failed_login($email) {
685        $this->db->insert_log(null, user::AUTH_NONE, 'failed attempt to log in as ' . $email);
686    }
687
688    public function get_num_logs() {
689        return $this->db->count_logs();
690    }
691
692    public function load_logs($from, $limit) {
693        return $this->db->load_logs($from, $limit);
694    }
695}