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