PageRenderTime 51ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/models/game.php

https://github.com/DYoung7/Zuluru
PHP | 1400 lines | 1165 code | 122 blank | 113 comment | 343 complexity | b2931597197db1fd01688dbc15ababb1 MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. class Game extends AppModel {
  3. var $name = 'Game';
  4. var $displayField = 'id';
  5. var $validate = array(
  6. 'home_score' => array(
  7. 'range' => array(
  8. 'rule' => array('valid_score', 0, 99),
  9. 'required' => false,
  10. 'allowEmpty' => false,
  11. 'message' => 'Scores must be in the range 0-99',
  12. 'on' => 'update',
  13. ),
  14. ),
  15. 'away_score' => array(
  16. 'range' => array(
  17. 'rule' => array('valid_score', 0, 99),
  18. 'required' => false,
  19. 'allowEmpty' => false,
  20. 'message' => 'Scores must be in the range 0-99',
  21. 'on' => 'update',
  22. ),
  23. ),
  24. 'status' => array(
  25. 'inlist' => array(
  26. 'rule' => array('inconfig', 'options.game_status'),
  27. 'required' => false,
  28. 'message' => 'You must select a valid status.',
  29. 'on' => 'update',
  30. ),
  31. ),
  32. 'round' => array(
  33. 'notempty' => array(
  34. 'rule' => array('notempty'),
  35. 'message' => 'TODO',
  36. ),
  37. ),
  38. );
  39. var $belongsTo = array(
  40. 'Division' => array(
  41. 'className' => 'Division',
  42. 'foreignKey' => 'division_id',
  43. ),
  44. 'GameSlot' => array(
  45. 'className' => 'GameSlot',
  46. 'foreignKey' => 'game_slot_id',
  47. ),
  48. 'Pool' => array(
  49. 'className' => 'Pool',
  50. 'foreignKey' => 'pool_id',
  51. ),
  52. 'HomeTeam' => array(
  53. 'className' => 'Team',
  54. 'foreignKey' => 'home_team',
  55. ),
  56. 'HomePoolTeam' => array(
  57. 'className' => 'PoolsTeam',
  58. 'foreignKey' => 'home_pool_team_id',
  59. ),
  60. 'AwayTeam' => array(
  61. 'className' => 'Team',
  62. 'foreignKey' => 'away_team',
  63. ),
  64. 'AwayPoolTeam' => array(
  65. 'className' => 'PoolsTeam',
  66. 'foreignKey' => 'away_pool_team_id',
  67. ),
  68. 'ApprovedBy' => array(
  69. 'className' => 'Person',
  70. 'foreignKey' => 'approved_by',
  71. )
  72. );
  73. var $hasMany = array(
  74. 'Allstar' => array(
  75. 'className' => 'Allstar',
  76. 'foreignKey' => 'game_id',
  77. 'dependent' => true,
  78. ),
  79. 'Attendance' => array(
  80. 'className' => 'Attendance',
  81. 'foreignKey' => 'game_id',
  82. 'dependent' => true,
  83. 'conditions' => array('team_event_id' => null),
  84. ),
  85. 'Incident' => array(
  86. 'className' => 'Incident',
  87. 'foreignKey' => 'game_id',
  88. 'dependent' => true,
  89. ),
  90. 'ScoreDetail' => array(
  91. 'className' => 'ScoreDetail',
  92. 'foreignKey' => 'game_id',
  93. 'dependent' => true,
  94. ),
  95. 'ScoreEntry' => array(
  96. 'className' => 'ScoreEntry',
  97. 'foreignKey' => 'game_id',
  98. 'dependent' => true,
  99. ),
  100. 'SpiritEntry' => array(
  101. 'className' => 'SpiritEntry',
  102. 'foreignKey' => 'game_id',
  103. 'dependent' => true,
  104. ),
  105. 'ScoreReminderEmail' => array(
  106. 'className' => 'ActivityLog',
  107. 'foreignKey' => 'game_id',
  108. 'dependent' => true,
  109. 'conditions' => array('type' => array('email_score_reminder', 'email_approval_notice')),
  110. ),
  111. 'ScoreMismatchEmail' => array(
  112. 'className' => 'ActivityLog',
  113. 'foreignKey' => 'game_id',
  114. 'dependent' => true,
  115. 'conditions' => array('type' => 'email_score_mismatch'),
  116. ),
  117. 'AttendanceReminderEmail' => array(
  118. 'className' => 'ActivityLog',
  119. 'foreignKey' => 'game_id',
  120. 'dependent' => true,
  121. 'conditions' => array('type' => array('email_attendance_reminder')),
  122. ),
  123. 'AttendanceSummaryEmail' => array(
  124. 'className' => 'ActivityLog',
  125. 'foreignKey' => 'game_id',
  126. 'dependent' => true,
  127. 'conditions' => array('type' => 'email_attendance_summary'),
  128. ),
  129. 'Note' => array(
  130. 'className' => 'Note',
  131. 'foreignKey' => 'game_id',
  132. 'dependent' => true,
  133. 'order' => 'Note.created',
  134. ),
  135. 'Stat' => array(
  136. 'className' => 'Stat',
  137. 'foreignKey' => 'game_id',
  138. 'dependent' => true,
  139. ),
  140. );
  141. static function compareDateAndField ($a, $b) {
  142. // Handle game, team event and task records
  143. if (!empty($a['GameSlot']['game_date'])) {
  144. $a_date = $a['GameSlot']['game_date'];
  145. $a_time = $a['GameSlot']['game_start'];
  146. } else if (!empty($a['TeamEvent']['date'])) {
  147. $a_date = $a['TeamEvent']['date'];
  148. $a_time = $a['TeamEvent']['start'];
  149. } else if (!empty($a['TaskSlot']['task_date'])) {
  150. $a_date = $a['TaskSlot']['task_date'];
  151. $a_time = $a['TaskSlot']['task_start'];
  152. } else {
  153. $a_date = $a_time = 0;
  154. }
  155. if (!empty($b['GameSlot']['game_date'])) {
  156. $b_date = $b['GameSlot']['game_date'];
  157. $b_time = $b['GameSlot']['game_start'];
  158. } else if (!empty($b['TeamEvent']['date'])) {
  159. $b_date = $b['TeamEvent']['date'];
  160. $b_time = $b['TeamEvent']['start'];
  161. } else if (!empty($b['TaskSlot']['task_date'])) {
  162. $b_date = $b['TaskSlot']['task_date'];
  163. $b_time = $b['TaskSlot']['task_start'];
  164. } else {
  165. $b_date = $b_time = 0;
  166. }
  167. if ($a_date < $b_date) {
  168. return -1;
  169. } else if ($a_date > $b_date) {
  170. return 1;
  171. }
  172. if ($a_time < $b_time) {
  173. return -1;
  174. } else if ($a_time > $b_time) {
  175. return 1;
  176. }
  177. // Handle named playoff games (and team events have names too)
  178. if (array_key_exists ('name', $a) && !empty ($a['name'])) {
  179. if (strpos($a['name'], '-') !== false) {
  180. list($x, $a_name) = explode('-', $a['name']);
  181. } else {
  182. $a_name = $a['name'];
  183. }
  184. if (strpos($b['name'], '-') !== false) {
  185. list($x, $b_name) = explode('-', $b['name']);
  186. } else {
  187. $b_name = $b['name'];
  188. }
  189. if ($a_name < $b_name) {
  190. return -1;
  191. } else if ($a_name > $b_name) {
  192. return 1;
  193. }
  194. }
  195. // Handle games based on field id
  196. if (!empty($a['GameSlot']['field_id']) && !empty($b['GameSlot']['field_id'])) {
  197. if ($a['GameSlot']['field_id'] < $b['GameSlot']['field_id']) {
  198. return -1;
  199. } else {
  200. return 1;
  201. }
  202. }
  203. // Handle tasks based on task slot end time and then id
  204. if (!empty($a['TaskSlot']['task_end']) && !empty($b['TaskSlot']['task_end'])) {
  205. if ($a['TaskSlot']['task_end'] < $b['TaskSlot']['task_end']) {
  206. return -1;
  207. } else if ($a['TaskSlot']['task_end'] > $b['TaskSlot']['task_end']) {
  208. return 1;
  209. } else if ($a['TaskSlot']['id'] < $b['TaskSlot']['id']) {
  210. return -1;
  211. } else {
  212. return 1;
  213. }
  214. }
  215. // Handle other things just based on their type
  216. if (!empty($a['GameSlot'])) {
  217. return -1;
  218. } else if (!empty($b['GameSlot'])) {
  219. return 1;
  220. } else if (!empty($a['TeamEvent'])) {
  221. return -1;
  222. } else if (!empty($b['TeamEvent'])) {
  223. return 1;
  224. }
  225. // Shouldn't ever reach here, but just in case...
  226. return -1;
  227. }
  228. static function _readDependencies (&$record) {
  229. if (!empty($record['Game']['home_dependency_type'])) {
  230. Game::_readDependency ($record['Game'], $record['HomePoolTeam'], 'home');
  231. } else if (!empty($record['home_dependency_type'])) {
  232. Game::_readDependency ($record, $record['HomePoolTeam'], 'home');
  233. }
  234. if (!empty($record['Game']['away_dependency_type'])) {
  235. Game::_readDependency ($record['Game'], $record['AwayPoolTeam'], 'away');
  236. } else if (!empty($record['away_dependency_type'])) {
  237. Game::_readDependency ($record, $record['AwayPoolTeam'], 'away');
  238. }
  239. }
  240. static function _readDependency (&$record, $pool, $type) {
  241. $ths = ClassRegistry::init ('Game');
  242. $id = $record["{$type}_dependency_id"];
  243. switch ($record["{$type}_dependency_type"]) {
  244. case 'game_winner':
  245. $game = $ths->field('name', array('id' => $id));
  246. $dependency = sprintf (__('Winner of game %s', true), $game);
  247. break;
  248. case 'game_loser':
  249. $game = $ths->field('name', array('id' => $id));
  250. $dependency = sprintf (__('Loser of game %s', true), $game);
  251. break;
  252. case 'seed':
  253. $dependency = sprintf (__('%s seed', true), ordinal($id));
  254. break;
  255. case 'pool':
  256. case 'copy':
  257. $dependency = Pool::_dependency($pool);
  258. $alias = $pool['alias'];
  259. if (!empty($alias)) {
  260. $dependency = "$alias [$dependency]";
  261. }
  262. break;
  263. }
  264. $record["{$type}_dependency"] = $dependency;
  265. }
  266. /**
  267. * We know that when we create tournament schedules, we create the most
  268. * important games first. So, when generating the bracket, we start with
  269. * the lowest-id game in the last round and work backwards, finding all
  270. * games that it depends on. As we place games in the bracket, we remove
  271. * them from the list. Repeat until there are no games left in that round,
  272. * and repeat that whole process until there are no rounds left.
  273. * This assumes that $games is indexed by game id.
  274. */
  275. static function _extractBracket(&$games) {
  276. $bracket = array();
  277. // Find the "most important" remaining game to start the bracket
  278. // TODO: Add some kind of "bracket sort" field and use that instead
  279. $pools = array_unique(Set::extract('/Game/pool_id', array('Game' => $games)));
  280. sort($pools);
  281. $pool = reset($pools);
  282. if ($pool === null) {
  283. $pools = array_unique(Set::extract('/Game/tournament_pool', array('Game' => $games)));
  284. sort($pools);
  285. $pool = reset($pools);
  286. $pool_field = 'tournament_pool';
  287. } else {
  288. $pool_field = 'pool_id';
  289. }
  290. $names = array_unique(Set::extract("/Game[$pool_field=$pool]/name", array('Game' => $games)));
  291. usort($names, array('Game', 'compare_game_name'));
  292. $name = reset($names);
  293. $final = reset(Set::extract("/Game[name=$name]/.", array('Game' => $games)));
  294. $bracket[$final['round']] = array($final);
  295. unset ($games[$final['id']]);
  296. $round = $final['round'];
  297. // Work backwards through previous rounds
  298. while ($round > 1) {
  299. $round_games = array();
  300. $empty = true;
  301. foreach ($bracket[$round] as $game) {
  302. if (!empty($game) && in_array($game['home_dependency_type'], array('game_winner', 'game_loser'))) {
  303. if (array_key_exists($game['home_dependency_id'], $games)) {
  304. $round_games[] = $games[$game['home_dependency_id']];
  305. $empty = false;
  306. unset ($games[$game['home_dependency_id']]);
  307. } else {
  308. $round_games[] = array();
  309. }
  310. } else {
  311. $round_games[] = array();
  312. }
  313. if (!empty($game) && in_array($game['away_dependency_type'], array('game_winner', 'game_loser'))) {
  314. if (array_key_exists($game['away_dependency_id'], $games)) {
  315. $round_games[] = $games[$game['away_dependency_id']];
  316. $empty = false;
  317. unset ($games[$game['away_dependency_id']]);
  318. } else {
  319. $round_games[] = array();
  320. }
  321. } else {
  322. $round_games[] = array();
  323. }
  324. }
  325. if ($empty) {
  326. break;
  327. }
  328. $bracket[--$round] = $round_games;
  329. }
  330. return $bracket;
  331. }
  332. static function compare_game_name($a, $b) {
  333. // First, check pool names, if they exist
  334. if (strpos($a, '-') !== false) {
  335. list ($a_pool, $a) = explode('-', $a);
  336. list ($b_pool, $b) = explode('-', $b);
  337. if ($a_pool < $b_pool) {
  338. return -1;
  339. } else if ($a_pool > $b_pool) {
  340. return 1;
  341. }
  342. }
  343. // Strip off any "st" or "nd" or "rd" or "th".
  344. // Change back to strings, so that later comparisons work.
  345. $a_ordinal = $b_ordinal = false;
  346. if (intval($a) > 0) {
  347. $a_num = strval(intval($a));
  348. if ($a != $a_num) {
  349. $a_ordinal = true;
  350. }
  351. }
  352. if (intval($b) > 0) {
  353. $b_num = strval(intval($b));
  354. if ($b != $b_num) {
  355. $b_ordinal = true;
  356. }
  357. }
  358. if ($a_ordinal && $b_ordinal) {
  359. if ($a_num < $b_num) {
  360. return -1;
  361. } else if ($a_num > $b_num) {
  362. return 1;
  363. }
  364. } else if ($a_ordinal && !$b_ordinal) {
  365. return -1;
  366. } else if (!$a_ordinal && $b_ordinal) {
  367. return 1;
  368. }
  369. if ($a < $b) {
  370. return -1;
  371. } else if ($a > $b) {
  372. return 1;
  373. }
  374. return 0;
  375. }
  376. function _validateForScheduleEdit() {
  377. foreach (array('home_score', 'away_score', 'status') as $field) {
  378. unset ($this->validate[$field]);
  379. }
  380. }
  381. function _saveGames($games, $publish) {
  382. // Make sure that some other coordinator hasn't scheduled a game in a
  383. // different league on one of the unused slots.
  384. $slot_ids = Set::extract ('/game_slot_id', $games);
  385. $game_ids = Set::extract ('/id', $games);
  386. $this->contain();
  387. $taken = $this->find('all', array('conditions' => array(
  388. 'game_slot_id' => $slot_ids,
  389. // Don't include game slots that are previously allocated to one of these games;
  390. // of course those will be taken, but it's okay!
  391. 'NOT' => array('id' => $game_ids),
  392. )));
  393. if (!empty ($taken)) {
  394. return array('text' => __('A game slot chosen for this schedule has been allocated elsewhere in the interim. Please try again.', true), 'class' => 'info');
  395. }
  396. $this->_validateForScheduleEdit();
  397. foreach (array_keys($games) as $key) {
  398. $games[$key]['published'] = $publish;
  399. }
  400. if (!$this->saveAll($games)) {
  401. return array('text' => __('Failed to save schedule changes!', true), 'class' => 'warning');
  402. }
  403. return true;
  404. }
  405. function updateFieldRanking(&$game, $field = null, $home = null, $away = null) {
  406. $homes = Configure::read('feature.home_field');
  407. $facilities = Configure::read('feature.facility_preference');
  408. $regions = Configure::read('feature.region_preference');
  409. if (!$homes && !$facilities && !$regions) {
  410. return true;
  411. }
  412. if ($field) {
  413. $field_id = $field['id'];
  414. $facility_id = $field['facility_id'];
  415. $region_id = $field['Facility']['region_id'];
  416. } else {
  417. $field_id = $this->GameSlot->field('field_id', array('GameSlot.id' => $game['game_slot_id']));
  418. if ($facilities || $regions) {
  419. $facility_id = $this->GameSlot->Field->field('facility_id', array('Field.id' => $field_id));
  420. if ($regions) {
  421. $region_id = $this->GameSlot->Field->Facility->field('region_id', array('Facility.id' => $facility_id));
  422. }
  423. }
  424. }
  425. foreach (array('home', 'away') as $team) {
  426. $rank = null;
  427. if ($team == 'home') {
  428. $some_preference = false;
  429. }
  430. if (!empty($game["{$team}_team"])) {
  431. $team_id = $game["{$team}_team"];
  432. if ($homes) {
  433. if (${$team}) {
  434. $home_field = ${$team}['home_field'];
  435. } else {
  436. $home_field = $this->HomeTeam->field('home_field', array('HomeTeam.id' => $team_id), 'HomeTeam.id');
  437. }
  438. if ($home_field == $field_id) {
  439. $rank = 1;
  440. } else if ($home_field != null && $team == 'home') {
  441. $some_preference = true;
  442. }
  443. }
  444. if (!$rank && $facilities) {
  445. if (${$team}) {
  446. $r = Set::extract("/Facility[id=$facility_id]/TeamsFacility/rank", ${$team});
  447. if (!empty($r)) {
  448. $rank = $r[0];
  449. } else if ($team == 'home' && !empty(${$team}['Facility'])) {
  450. $some_preference = true;
  451. }
  452. } else {
  453. $r = $this->HomeTeam->TeamsFacility->find('first', array(
  454. 'conditions' => array(
  455. 'team_id' => $team_id,
  456. 'facility_id' => $facility_id,
  457. ),
  458. 'fields' => array('TeamsFacility.rank'),
  459. ));
  460. if ($r) {
  461. $rank = $r['TeamsFacility']['rank'];
  462. } else if ($team == 'home') {
  463. $count = $this->HomeTeam->TeamsFacility->find('count', array(
  464. 'conditions' => array(
  465. 'team_id' => $team_id,
  466. ),
  467. ));
  468. if ($count != 0) {
  469. $some_preference = true;
  470. }
  471. }
  472. }
  473. }
  474. if (!$rank && $regions) {
  475. if (${$team}) {
  476. $home_region = ${$team}['region_preference'];
  477. } else {
  478. $home_region = $this->HomeTeam->field('region_preference', array('HomeTeam.id' => $team_id), 'HomeTeam.id');
  479. }
  480. if ($home_region == $region_id) {
  481. if (${$team}) {
  482. $r = count(${$team}['Facility']);
  483. } else {
  484. $r = $this->HomeTeam->TeamsFacility->find('count', array(
  485. 'conditions' => array(
  486. 'team_id' => $team_id,
  487. ),
  488. ));
  489. }
  490. // A regional match won't count as more preferred than
  491. // a 2. This will give teams with regional preferences
  492. // a slight advantage over teams with specific field
  493. // preferences in terms of how often they're likely
  494. // to have their preferences met.
  495. $rank = max(2, $r + 1);
  496. } else if ($team == 'home' && $home_region !== null) {
  497. $some_preference = true;
  498. }
  499. }
  500. }
  501. $game["{$team}_field_rank"] = $rank;
  502. }
  503. // If this is a field that the away team likes more than the home
  504. // team, swap the teams, so that the current home team doesn't get
  505. // penalized in future field selections. But only do it when we're
  506. // building a schedule, not when we're editing.
  507. if (!array_key_exists('id', $game) && $game['away_field_rank'] !== null && $some_preference &&
  508. ($game['home_field_rank'] === null || $game['home_field_rank'] > $game['away_field_rank'])
  509. )
  510. {
  511. list ($game['home_team'], $game['home_field_rank'], $game['away_team'], $game['away_field_rank']) =
  512. array($game['away_team'], $game['away_field_rank'], $game['home_team'], $game['home_field_rank']);
  513. }
  514. return true;
  515. }
  516. /**
  517. * Adjust the indices of the ScoreEntry and SpiritEntry, so that
  518. * the arrays are indexed by team_id instead of from zero.
  519. *
  520. */
  521. static function _adjustEntryIndices(&$game) {
  522. foreach (array('ScoreEntry' => 'team_id', 'SpiritEntry' => 'team_id', 'ScoreReminderEmail' => 'team_id') as $model => $field) {
  523. self::_reindexInner($game, $model, $field);
  524. }
  525. }
  526. /**
  527. * Retrieve score entry for given team. Assumes that _adjustEntryIndices has been called.
  528. *
  529. * @return mixed Array with the requested score entry, or false if the team hasn't entered a final score yet.
  530. */
  531. static function _get_score_entry ($game, $team_id)
  532. {
  533. if (array_key_exists ($team_id, $game['ScoreEntry']) && $game['ScoreEntry'][$team_id]['status'] != 'in_progress') {
  534. return $game['ScoreEntry'][$team_id];
  535. }
  536. return false;
  537. }
  538. /**
  539. * Retrieve the best score entry for a game.
  540. *
  541. * @return mixed Array with the best score entry, false if neither team has entered a score yet,
  542. * or null if there is no clear "best" entry.
  543. */
  544. static function _get_best_score_entry ($game)
  545. {
  546. switch (count($game['ScoreEntry'])) {
  547. case 0:
  548. return false;
  549. case 1:
  550. return array_pop($game['ScoreEntry']);
  551. case 2:
  552. $entries = array_values($game['ScoreEntry']);
  553. if (Game::_score_entries_agree($entries[0], $entries[1])) {
  554. return $entries[0];
  555. } else if ($entries[0]['status'] == 'in_progress' && $entries[1]['status'] != 'in_progress') {
  556. return $entries[1];
  557. } else if ($entries[0]['status'] != 'in_progress' && $entries[1]['status'] == 'in_progress') {
  558. return $entries[0];
  559. } else if ($entries[0]['status'] == 'in_progress' && $entries[1]['status'] == 'in_progress') {
  560. return ($entries[0]['updated'] > $entries[1]['updated'] ? $entries[0] : $entries[1]);
  561. }
  562. }
  563. return null;
  564. }
  565. /**
  566. * Retrieve spirit entry for given team. Assumes that _adjustEntryIndices has been called.
  567. *
  568. * @return mixed Array with the requested spirit entry, or false if the other team hasn't entered spirit yet.
  569. */
  570. static function _get_spirit_entry ($game, $team_id, &$spirit_obj)
  571. {
  572. $entry = false;
  573. if (array_key_exists ('SpiritEntry', $game) && array_key_exists ($team_id, $game['SpiritEntry'])) {
  574. $entry = $game['SpiritEntry'][$team_id];
  575. }
  576. if (isset($spirit_obj) && Configure::read('scoring.spirit_default')) {
  577. if ($game['status'] == 'home_default') {
  578. if ($team_id == $game['home_team']) {
  579. $entry = $spirit_obj->defaulted();
  580. } else {
  581. $entry = $spirit_obj->expected();
  582. }
  583. } else if ($game['status'] == 'away_default') {
  584. if ($team_id == $game['home_team']) {
  585. $entry = $spirit_obj->expected();
  586. } else {
  587. $entry = $spirit_obj->defaulted();
  588. }
  589. }
  590. if ($entry == false) {
  591. $entry = $spirit_obj->expected();
  592. }
  593. }
  594. return $entry;
  595. }
  596. /**
  597. * Compare two score entries
  598. */
  599. static function _score_entries_agree ($one, $two)
  600. {
  601. if ($one['status'] == $two['status']) {
  602. if (in_array($one['status'], array('normal', 'in_progress'))) {
  603. return (($one['score_for'] == $two['score_against']) && ($one['score_against'] == $two['score_for']));
  604. }
  605. return true;
  606. }
  607. return false;
  608. }
  609. static function _is_finalized($game) {
  610. if (array_key_exists ('Game', $game)) {
  611. $test = $game['Game'];
  612. } else {
  613. $test = $game;
  614. }
  615. return (!empty($test['status']) && $test['status'] != 'normal' || isset($test['home_score']));
  616. }
  617. /**
  618. * Read the attendance records for a game.
  619. * This will also create any missing records, with "unknown" status.
  620. *
  621. * @param mixed $team The team to read attendance for.
  622. * @param mixed $days The days on which the division operates.
  623. * @param mixed $game_id The game id, if known.
  624. * @param mixed $date The date of the game, or an array of dates.
  625. * @param mixed $force If true, teams without attendance tracking will have a "default" attendance array generated; otherwise, they will get an empty array
  626. * @return mixed List of attendance records.
  627. *
  628. */
  629. function _read_attendance($team, $days, $game_id, $dates = null, $force = false) {
  630. // We accept either a pre-read team array with roster info, or just an id
  631. if (!is_array($team)) {
  632. $team_id = $team;
  633. $this->Attendance->Team->contain (array(
  634. 'Person' => array(
  635. 'fields' => array('Person.id', 'Person.first_name', 'Person.last_name', 'Person.gender'),
  636. ),
  637. ));
  638. $team = $this->Attendance->Team->read(null, $team_id);
  639. $track_attendance = $team['Team']['track_attendance'];
  640. } else if (array_key_exists ('id', $team)) {
  641. $team_id = $team['id'];
  642. $track_attendance = $team['track_attendance'];
  643. } else {
  644. $team_id = $team['Team']['id'];
  645. $track_attendance = $team['Team']['track_attendance'];
  646. }
  647. if (!$track_attendance) {
  648. // Teams without attendance tracking may get no data.
  649. // This shouldn't actually ever happen, and is really only in
  650. // place to help detect coding errors elsewhere.
  651. if (!$force) {
  652. return array();
  653. }
  654. // Make up data that looks like what we'd have if tracking was enabled.
  655. if (is_array($dates)) {
  656. trigger_error('Forcing attendance records for multiple dates for teams without attendance tracking enabled is not yet supported.', E_USER_ERROR);
  657. } else if (!$game_id) {
  658. trigger_error('Forcing attendance records for unscheduled games for teams without attendance tracking enabled is not yet supported.', E_USER_ERROR);
  659. } else {
  660. return $this->_forced_attendance($team, $game_id);
  661. }
  662. }
  663. // Make sure that all required records exist
  664. if (is_array($dates)) {
  665. foreach ($dates as $date) {
  666. $this->_create_attendance($team, $days, null, $date);
  667. }
  668. $conditions = array('game_date' => Game::_matchDates($dates, $days));
  669. } else {
  670. $this->_create_attendance($team, $days, $game_id, $dates);
  671. if ($game_id !== null) {
  672. $conditions = array('game_id' => $game_id);
  673. } else {
  674. $conditions = array('game_date' => Game::_matchDates($dates, $days));
  675. }
  676. }
  677. // Re-read whatever is current, including join tables that will be useful in the output
  678. $this->Attendance->Team->contain (array(
  679. 'Person' => array(
  680. Configure::read('security.auth_model'),
  681. 'Attendance' => array(
  682. 'conditions' => array_merge (array('team_id' => $team_id, 'team_event_id' => null), $conditions),
  683. ),
  684. 'Setting' => array(
  685. 'conditions' => array('category' => 'personal', 'name' => 'attendance_emails'),
  686. ),
  687. 'conditions' => array('TeamsPerson.status' => ROSTER_APPROVED),
  688. 'fields' => array(
  689. 'Person.id', 'Person.first_name', 'Person.last_name', 'Person.gender', 'Person.skill_level',
  690. ),
  691. ),
  692. ));
  693. $attendance = $this->Attendance->Team->read(null, $team_id);
  694. // There may be other attendance records from people that are no longer on the roster
  695. $this->Attendance->contain (array(
  696. 'Person' => array(
  697. Configure::read('security.auth_model'),
  698. 'fields' => array(
  699. 'Person.id', 'Person.first_name', 'Person.last_name', 'Person.gender', 'Person.skill_level',
  700. ),
  701. ),
  702. ));
  703. $extra = $this->Attendance->find('all', array(
  704. 'conditions' => array_merge($conditions, array(
  705. 'team_id' => $team_id,
  706. 'team_event_id' => null,
  707. 'NOT' => array('person_id' => Set::extract('/Person/id', $attendance)),
  708. )),
  709. ));
  710. // Mangle these records into the same format as from the read above
  711. $new = array();
  712. foreach ($extra as $person) {
  713. if (!array_key_exists($person['Person']['id'], $new)) {
  714. $new[$person['Person']['id']] = array_merge ($person['Person'], array(
  715. 'Attendance' => array(),
  716. 'TeamsPerson' => array('role' => 'none', 'status' => ROSTER_APPROVED),
  717. ));
  718. }
  719. $new[$person['Person']['id']]['Attendance'][] = $person['Attendance'];
  720. }
  721. $attendance['Person'] = array_merge ($attendance['Person'], $new);
  722. usort ($attendance['Person'], array('Team', 'compareRoster'));
  723. return $attendance;
  724. }
  725. function _create_attendance($team, $days, $game_id, $date) {
  726. if (array_key_exists ('id', $team)) {
  727. $team_id = $team['id'];
  728. } else {
  729. $team_id = $team['Team']['id'];
  730. }
  731. // Find game details
  732. if ($game_id !== null) {
  733. $this->contain (array(
  734. 'GameSlot',
  735. ));
  736. $game = $this->read(null, $game_id);
  737. if (!$game) {
  738. return;
  739. }
  740. if ($game['Game']['home_team'] != $team_id && $game['Game']['away_team'] != $team_id) {
  741. return;
  742. }
  743. $date = $game['GameSlot']['game_date'];
  744. // Find all attendance records for this team for this game
  745. $attendance = $this->Attendance->find('all', array(
  746. 'contain' => false,
  747. 'conditions' => array(
  748. 'team_id' => $team_id,
  749. 'game_id' => $game_id,
  750. ),
  751. ));
  752. if (empty ($attendance)) {
  753. $match_dates = Game::_matchDates($date, $days);
  754. // There might be no attendance records because of a schedule change.
  755. // Check for other attendance records for this team on the same date.
  756. $attendance = $this->Attendance->find('all', array(
  757. 'contain' => false,
  758. 'conditions' => array(
  759. 'team_id' => $team_id,
  760. 'game_date' => $match_dates,
  761. 'team_event_id' => null,
  762. ),
  763. ));
  764. $attendance_game_ids = array_unique (Set::extract('/Attendance/game_id', $attendance));
  765. // Check for other scheduled games including this team on the same date.
  766. $this->contain('GameSlot');
  767. $games = $this->find('all', array(
  768. 'conditions' => array(
  769. 'OR' => array(
  770. 'Game.home_team' => $team_id,
  771. 'Game.away_team' => $team_id,
  772. ),
  773. 'GameSlot.game_date' => $match_dates,
  774. 'Game.id !=' => $game_id,
  775. ),
  776. 'order' => 'GameSlot.game_start',
  777. ));
  778. $scheduled_game_ids = array_unique (Set::extract('/Game/id', $games));
  779. if (count($attendance_game_ids) > count($scheduled_game_ids)) {
  780. // If there are more other games with attendance records than there
  781. // are other games scheduled, then one of those games must be this
  782. // game, but it was rescheduled. Figure out which one.
  783. // Note that this guess may not be right when a team has more than
  784. // one game that gets rescheduled; this will hopefully be a very
  785. // rare circumstance.
  786. foreach ($attendance_game_ids as $i) {
  787. if (!in_array($i, $scheduled_game_ids)) {
  788. $rescheduled_game_id = $i;
  789. break;
  790. }
  791. }
  792. } else {
  793. // Otherwise, this game is a new one. If there are other attendance
  794. // records, we'll copy them.
  795. $copy_from_game_id = reset($attendance_game_ids);
  796. }
  797. }
  798. } else if ($date !== null) {
  799. $match_dates = Game::_matchDates($date, $days);
  800. $this->contain('GameSlot');
  801. $games = $this->find('all', array(
  802. 'conditions' => array(
  803. 'OR' => array(
  804. 'Game.home_team' => $team_id,
  805. 'Game.away_team' => $team_id,
  806. ),
  807. 'GameSlot.game_date' => $match_dates,
  808. 'Game.published' => true,
  809. ),
  810. 'order' => 'GameSlot.game_start',
  811. ));
  812. if (empty($games)) {
  813. // Find all game attendance records for this team for this date
  814. $attendance = $this->Attendance->find('all', array(
  815. 'contain' => false,
  816. 'conditions' => array(
  817. 'team_id' => $team_id,
  818. 'game_date' => $match_dates,
  819. 'team_event_id' => null,
  820. ),
  821. ));
  822. } else {
  823. foreach ($games as $game) {
  824. $this->_create_attendance($team, $days, $game['Game']['id'], $date);
  825. }
  826. return;
  827. }
  828. } else {
  829. return;
  830. }
  831. // Extract list of players on the roster as of this date
  832. $roster = Set::extract ("/Person/TeamsPerson[created<=$date][status=" . ROSTER_APPROVED ."]/../.", $team);
  833. // Go through the roster and make sure there are records for all players on this date.
  834. $attendance_update = array();
  835. foreach ($roster as $person) {
  836. $update = false;
  837. $conditions = "[person_id={$person['id']}]";
  838. if (isset($copy_from_game_id)) {
  839. $conditions .= "[game_id=$copy_from_game_id]";
  840. } else if (isset($rescheduled_game_id)) {
  841. // We might need to update an existing record with a rescheduled game id.
  842. $conditions .= "[game_id=$rescheduled_game_id]";
  843. }
  844. $record = Set::extract ("/Attendance$conditions/.", $attendance);
  845. // Any record we have at this point is either something to copy from,
  846. // rescheduled or a new game on a date that we already had a placeholder
  847. // record for, or correct.
  848. if (!empty ($record)) {
  849. if (isset($copy_from_game_id)) {
  850. $update = $record[0];
  851. $update['game_id'] = $game_id;
  852. unset($update['id']);
  853. } else if ($game_id != $record[0]['game_id']) {
  854. $update = array(
  855. 'id' => $record[0]['id'],
  856. 'game_id' => $game_id,
  857. 'game_date' => $date,
  858. // Preserve the last update time, don't overwrite with "now"
  859. 'updated' => $record[0]['updated'],
  860. );
  861. }
  862. } else {
  863. // We didn't find any appropriate record, so create a new one
  864. $update = array(
  865. 'team_id' => $team_id,
  866. 'game_date' => $date,
  867. 'game_id' => $game_id,
  868. 'person_id' => $person['id'],
  869. 'status' => ATTENDANCE_UNKNOWN,
  870. );
  871. }
  872. if ($update) {
  873. $attendance_update[] = $update;
  874. }
  875. }
  876. if (!empty ($attendance_update)) {
  877. $this->Attendance->saveAll($attendance_update);
  878. }
  879. }
  880. function _forced_attendance($team, $game_id) {
  881. if (array_key_exists ('id', $team)) {
  882. $team_id = $team['id'];
  883. } else {
  884. $team_id = $team['Team']['id'];
  885. }
  886. // Find game details
  887. $this->contain ();
  888. $game = $this->read(null, $game_id);
  889. if (!$game) {
  890. return array();
  891. }
  892. if ($game['Game']['home_team'] != $team_id && $game['Game']['away_team'] != $team_id) {
  893. return array();
  894. }
  895. // Go through the roster and make fake records for all players on this date.
  896. $player_roles = Configure::read('regular_roster_roles');
  897. foreach ($team['Person'] as $key => $person) {
  898. if ($person['TeamsPerson']['status'] == ROSTER_APPROVED) {
  899. if (in_array($person['TeamsPerson']['role'], $player_roles)) {
  900. $status = ATTENDANCE_ATTENDING;
  901. } else {
  902. $status = ATTENDANCE_UNKNOWN;
  903. }
  904. $team['Person'][$key]['Attendance'] = array(array(
  905. 'team_id' => $team_id,
  906. 'game_id' => $game_id,
  907. 'person_id' => $person['id'],
  908. 'status' => $status,
  909. 'comment' => null,
  910. ));
  911. }
  912. }
  913. usort ($team['Person'], array('Team', 'compareRoster'));
  914. return $team;
  915. }
  916. static function _matchDates($dates, $days) {
  917. if (!is_array($dates)) {
  918. $dates = array($dates);
  919. }
  920. $match_dates = array();
  921. foreach ($dates as $date) {
  922. $date_time = strtotime($date . ' 12:00:00');
  923. $date_day = date('w', $date_time) + 1;
  924. foreach ($days as $day) {
  925. $match_dates[] = date('Y-m-d', $date_time + ($day - $date_day) * DAY);
  926. }
  927. }
  928. return $match_dates;
  929. }
  930. static function _attendanceOptions($team_id, $role, $status, $past, $is_captain) {
  931. $is_regular = in_array($role, Configure::read('playing_roster_roles'));
  932. $options = Configure::read('attendance');
  933. // Only a captain can mark someone as a no show for a past game
  934. if (!$is_captain || !$past) {
  935. unset($options[ATTENDANCE_NO_SHOW]);
  936. }
  937. // Invited and available are only for subs
  938. if ($is_regular) {
  939. unset($options[ATTENDANCE_INVITED]);
  940. unset($options[ATTENDANCE_AVAILABLE]);
  941. } else if (!$is_captain) {
  942. // What a sub can set themselves to depends on their current status
  943. switch ($status) {
  944. case ATTENDANCE_UNKNOWN:
  945. case ATTENDANCE_ABSENT:
  946. case ATTENDANCE_AVAILABLE:
  947. unset($options[ATTENDANCE_ATTENDING]);
  948. unset($options[ATTENDANCE_INVITED]);
  949. break;
  950. case ATTENDANCE_ATTENDING:
  951. unset($options[ATTENDANCE_INVITED]);
  952. unset($options[ATTENDANCE_AVAILABLE]);
  953. break;
  954. case ATTENDANCE_INVITED:
  955. unset($options[ATTENDANCE_UNKNOWN]);
  956. unset($options[ATTENDANCE_AVAILABLE]);
  957. break;
  958. }
  959. }
  960. return $options;
  961. }
  962. static function twitterScore($team, $team_score, $opponent, $opponent_score) {
  963. if ($team_score >= $opponent_score) {
  964. return Team::twitterName($team) . ' ' . $team_score . ', ' . Team::twitterName($opponent) . ' ' . $opponent_score;
  965. } else {
  966. return Team::twitterName($opponent) . ' ' . $opponent_score . ', ' . Team::twitterName($team) . ' ' . $team_score;
  967. }
  968. }
  969. function affiliate($id) {
  970. return $this->Division->affiliate($this->field('division_id', array('Game.id' => $id)));
  971. }
  972. function afterSave() {
  973. if (!empty($this->data['Game']['game_slot_id'])) {
  974. $this->GameSlot->id = $this->data['Game']['game_slot_id'];
  975. if (!$this->GameSlot->saveField('assigned', true)) {
  976. return false;
  977. }
  978. }
  979. if (Configure::read('feature.badges') && $this->_is_finalized($this->data)) {
  980. $badge_obj = AppController::_getComponent('Badge');
  981. if (!$badge_obj->update('game', $this->data)) {
  982. return false;
  983. }
  984. }
  985. }
  986. function _validateAndSaveSchedule($data, $available_slots, $teams = null) {
  987. $publish = $data['Game']['publish'];
  988. unset ($data['Game']['publish']);
  989. if (array_key_exists('double_header', $data['Game'])) {
  990. $allow_double_header = $data['Game']['double_header'];
  991. unset ($data['Game']['double_header']);
  992. } else {
  993. $allow_double_header = false;
  994. }
  995. if (array_key_exists('multiple_days', $data['Game'])) {
  996. $allow_multiple_days = $data['Game']['multiple_days'];
  997. unset ($data['Game']['multiple_days']);
  998. } else {
  999. $allow_multiple_days = false;
  1000. }
  1001. if (array_key_exists('double_booking', $data['Game'])) {
  1002. $allow_double_booking = $data['Game']['double_booking'];
  1003. unset ($data['Game']['double_booking']);
  1004. } else {
  1005. $allow_double_booking = false;
  1006. }
  1007. if (array_key_exists('cross_division', $data['Game'])) {
  1008. $allow_cross_division = $data['Game']['cross_division'];
  1009. unset ($data['Game']['cross_division']);
  1010. } else {
  1011. $allow_cross_division = false;
  1012. }
  1013. // TODO: Remove workaround for Set::extract bug
  1014. $data['Game'] = array_values($data['Game']);
  1015. $used_slots = Set::extract ('/Game/game_slot_id', $data);
  1016. if (in_array ('', $used_slots)) {
  1017. return array('text' => __('You cannot choose the "---" as the game time/place!', true), 'class' => 'info');
  1018. }
  1019. if (!$allow_double_booking) {
  1020. $slot_counts = array_count_values ($used_slots);
  1021. foreach ($slot_counts as $slot_id => $count) {
  1022. if ($count > 1) {
  1023. $this->GameSlot->contain(array(
  1024. 'Field' => 'Facility',
  1025. ));
  1026. $slot = $this->GameSlot->read(null, $slot_id);
  1027. $slot_field = $slot['Field']['long_name'];
  1028. $slot_time = "{$slot['GameSlot']['game_date']} {$slot['GameSlot']['game_start']}";
  1029. return array('text' => sprintf (__('Game slot at %s on %s was selected more than once!', true), $slot_field, $slot_time), 'class' => 'info');
  1030. }
  1031. }
  1032. }
  1033. $team_ids = array_merge (
  1034. Set::extract ('/Game/home_team', $data),
  1035. Set::extract ('/Game/away_team', $data)
  1036. );
  1037. if (!empty($team_ids)) {
  1038. if (in_array ('', $team_ids)) {
  1039. return array('text' => __('You cannot choose the "---" as the team!', true), 'class' => 'info');
  1040. }
  1041. $team_names = $this->Division->Team->find('list', array(
  1042. 'contain' => false,
  1043. 'conditions' => array('Team.id' => $team_ids),
  1044. ));
  1045. $team_counts = array_count_values ($team_ids);
  1046. foreach ($team_counts as $team_id => $count) {
  1047. if ($count > 1) {
  1048. if ($allow_double_header || $allow_multiple_days) {
  1049. // Check that the double-header doesn't cause conflicts; must be at the same facility, but different times
  1050. $team_slot_ids = array_merge(
  1051. Set::extract ("/Game[home_team=$team_id]/game_slot_id", $data),
  1052. Set::extract ("/Game[away_team=$team_id]/game_slot_id", $data)
  1053. );
  1054. if (count ($team_slot_ids) != count (array_unique ($team_slot_ids))) {
  1055. return array('text' => sprintf (__('Team %s was scheduled twice in the same time slot!', true), $team_names[$team_id]), 'class' => 'info');
  1056. }
  1057. $this->GameSlot->contain(array(
  1058. 'Field',
  1059. ));
  1060. $team_slots = $this->GameSlot->find('all', array('conditions' => array(
  1061. 'GameSlot.id' => $team_slot_ids,
  1062. )));
  1063. foreach ($team_slots as $key1 => $slot1) {
  1064. foreach ($team_slots as $key2 => $slot2) {
  1065. if ($key1 != $key2) {
  1066. if (!$allow_double_header && $slot1['GameSlot']['game_date'] == $slot2['GameSlot']['game_date']) {
  1067. return array('text' => sprintf (__('Team %s was scheduled twice on the same day!', true), $team_names[$team_id]), 'class' => 'info');
  1068. }
  1069. if (!$allow_multiple_days && $slot1['GameSlot']['game_date'] != $slot2['GameSlot']['game_date']) {
  1070. return array('text' => sprintf (__('Team %s was scheduled on different days!', true), $team_names[$team_id]), 'class' => 'info');
  1071. }
  1072. if ($slot1['GameSlot']['game_date'] == $slot2['GameSlot']['game_date'] &&
  1073. $slot1['GameSlot']['game_start'] >= $slot2['GameSlot']['game_start'] &&
  1074. $slot1['GameSlot']['game_start'] < $slot2['GameSlot']['display_game_end'])
  1075. {
  1076. return array('text' => sprintf (__('Team %s was scheduled in overlapping time slots!', true), $team_names[$team_id]), 'class' => 'info');
  1077. }
  1078. if ($slot1['GameSlot']['game_date'] == $slot2['GameSlot']['game_date'] && $slot1['Field']['facility_id'] != $slot2['Field']['facility_id']) {
  1079. return array('text' => sprintf (__('Team %s was scheduled on %s at different facilities!', true), $team_names[$team_id], Configure::read('ui.fields')), 'class' => 'info');
  1080. }
  1081. }
  1082. }
  1083. }
  1084. } else {
  1085. return array('text' => sprintf (__('Team %s was selected more than once!', true), $team_names[$team_id]), 'class' => 'info');
  1086. }
  1087. }
  1088. }
  1089. $team_divisions = $this->Division->Team->find('list', array(
  1090. 'contain' => array(),
  1091. 'fields' => array('Team.id', 'Team.division_id'),
  1092. 'conditions' => array('Team.id' => $team_ids),
  1093. ));
  1094. }
  1095. $seeds = array_merge (
  1096. Set::extract ('/Game/home_pool_team_id', $data),
  1097. Set::extract ('/Game/away_pool_team_id', $data)
  1098. );
  1099. if (!empty($seeds)) {
  1100. if (in_array ('', $seeds)) {
  1101. return array('text' => __('You cannot choose the "---" as the seed!', true), 'class' => 'info');
  1102. }
  1103. $seed_names = $this->Division->Game->Pool->PoolsTeam->find('list', array(
  1104. 'contain' => false,
  1105. 'conditions' => array('PoolsTeam.id' => $seeds),
  1106. ));
  1107. $seed_counts = array_count_values ($seeds);
  1108. foreach ($seed_counts as $seed_id => $count) {
  1109. if ($count > 1) {
  1110. // Check that the double-header doesn't cause conflicts; must be at the same facility, but different times
  1111. $seed_slot_ids = array_merge(
  1112. Set::extract ("/Game[home_pool_team_id=$seed_id]/game_slot_id", $data),
  1113. Set::extract ("/Game[away_pool_team_id=$seed_id]/game_slot_id", $data)
  1114. );
  1115. if (count ($seed_slot_ids) != count (array_unique ($seed_slot_ids))) {
  1116. return array('text' => sprintf (__('Seed %s was scheduled twice in the same time slot!', true), $seed_names[$seed_id]), 'class' => 'info');
  1117. }
  1118. $this->GameSlot->contain(array(
  1119. 'Field',
  1120. ));
  1121. $seed_slots = $this->GameSlot->find('all', array('conditions' => array(
  1122. 'GameSlot.id' => $seed_slot_ids,
  1123. )));
  1124. foreach ($seed_slots as $key1 => $slot1) {
  1125. foreach ($seed_slots as $key2 => $slot2) {
  1126. if ($key1 != $key2) {
  1127. if ($slot1['GameSlot']['game_date'] == $slot2['GameSlot']['game_date'] &&
  1128. $slot1['GameSlot']['game_start'] >= $slot2['GameSlot']['game_start'] &&
  1129. $slot1['GameSlot']['game_start'] < $slot2['GameSlot']['display_game_end'])
  1130. {
  1131. return array('text' => sprintf (__('Seed %s was scheduled in overlapping time slots!', true), $seed_names[$seed_id]), 'class' => 'info');
  1132. }
  1133. if ($slot1['GameSlot']['game_date'] == $slot2['GameSlot']['game_date'] && $slot1['Field']['facility_id'] != $slot2['Field']['facility_id']) {
  1134. return array('text' => sprintf (__('Seed %s was scheduled on %s at different facilities!', true), $seed_names[$seed_id], Configure::read('ui.fields')), 'class' => 'info');
  1135. }
  1136. }
  1137. }
  1138. }
  1139. }
  1140. }
  1141. $seed_divisions = $this->Division->Game->Pool->PoolsTeam->find('list', array(
  1142. 'contain' => array(),
  1143. 'joins' => array(
  1144. array(
  1145. 'table' => "{$this->tablePrefix}pools",
  1146. 'alias' => 'Pool',
  1147. 'type' => 'LEFT',
  1148. 'foreignKey' => false,
  1149. 'conditions' => 'PoolsTeam.pool_id = Pool.id',
  1150. ),
  1151. ),
  1152. 'fields' => array('PoolsTeam.id', 'Pool.division_id'),
  1153. 'conditions' => array('PoolsTeam.id' => $seeds),
  1154. ));
  1155. }
  1156. $no_dependencies = array_merge (
  1157. Set::extract ('/Game[home_dependency_type=]', $data),
  1158. Set::extract ('/Game[away_dependency_type=]', $data)
  1159. );
  1160. if (!empty($no_dependencies)) {
  1161. return array('text' => __('You cannot choose the "---" as the dependency type!', true), 'class' => 'info');
  1162. }
  1163. $winners = array_merge (
  1164. Set::extract ('/Game[home_dependency_type=game_winner]/home_dependency_id', $data),
  1165. Set::extract ('/Game[away_dependency_type=game_winner]/away_dependency_id', $data)
  1166. );
  1167. if (!empty($winners)) {
  1168. if (in_array ('', $winners)) {
  1169. return array('text' => __('You cannot choose the "---" as the game dependency!', true), 'class' => 'info');
  1170. }
  1171. $game_names = $this->Division->Game->find('list', array(
  1172. 'contain' => false,
  1173. 'fields' => array('Game.id', 'Game.name'),
  1174. 'conditions' => array('Game.id' => $winners),
  1175. ));
  1176. $winner_counts = array_count_values ($winners);
  1177. foreach ($winner_counts as $winner_id => $count) {
  1178. if ($count > 1) {
  1179. return array('text' => sprintf (__('Winner of game %s was selected more than once!', true), $game_names[$winner_id]), 'class' => 'info');
  1180. }
  1181. }
  1182. }
  1183. $losers = array_merge (
  1184. Set::extract ('/Game[home_dependency_type=game_loser]/home_dependency_id', $data),
  1185. Set::extract ('/Game[away_dependency_type=game_loser]/away_dependency_id', $data)
  1186. );
  1187. if (!empty($losers)) {
  1188. if (in_array ('', $losers)) {
  1189. return array('text' => __('You cannot choose the "---" as the game dependency!', true), 'class' => 'info');
  1190. }
  1191. $game_names = $this->Division->Game->find('list', array(
  1192. 'contain' => false,
  1193. 'fields' => array('Game.id', 'Game.name'),
  1194. 'conditions' => array('Game.id' => $losers),
  1195. ));
  1196. $loser_counts = array_count_values ($losers);
  1197. foreach ($loser_counts as $loser_id => $count) {
  1198. if ($count > 1) {
  1199. return array('text' => sprintf (__('Loser of game %s was selected more than once!', true), $game_names[$loser_id]), 'class' => 'info');
  1200. }
  1201. }
  1202. }
  1203. if (!empty($winners) || !empty($losers)) {
  1204. $game_divisions = $this->Division->Game->find('list', array(
  1205. 'contain' => array(),
  1206. 'fields' => array('Game.id', 'Game.division_id'),
  1207. 'conditions' => array('Game.id' => array_merge($winners, $losers)),
  1208. ));
  1209. }
  1210. if ($teams) {
  1211. $this->_reindexOuter($teams, 'Team', 'id');
  1212. }
  1213. foreach ($data['Game'] as $key => $game) {
  1214. if (array_key_exists('home_team', $game)) {
  1215. $home_division = $team_divisions[$game['home_team']];
  1216. $home_name = $team_names[$game['home_team']];
  1217. } else if (array_key_exists('home_pool_team_id', $game)) {
  1218. $home_division = $seed_divisions[$game['home_pool_team_id']];
  1219. $home_name = $seed_names[$game['home_pool_team_id']];
  1220. } else if (array_key_exists('home_dependency_id', $game)) {
  1221. $home_division = $game_divisions[$game['home_dependency_id']];
  1222. $home_name = $game['home_dependency_type'] . ' ' . $game_names[$game['home_dependency_id']];
  1223. }
  1224. if (array_key_exists('away_team', $game)) {
  1225. $away_division = $team_divisions[$game['away_team']];
  1226. $away_name = $team_names[$game['away_team']];
  1227. } else if (array_key_exists('away_pool_team_id', $game)) {
  1228. $away_division = $seed_divisions[$game['away_pool_team_id']];
  1229. $away_name = $seed_names[$game['away_pool_team_id']];
  1230. } else if (array_key_exists('away_dependency_id', $game)) {
  1231. $away_division = $game_divisions[$game['away_dependency_id']];
  1232. $away_name = $game['away_dependency_type'] . ' ' . $game_names[$game['away_dependency_id']];
  1233. }
  1234. if ($home_division != $away_division && !$allow_cross_division) {
  1235. return array('text' => sprintf(__('You have scheduled teams from different divisions against each other (%s vs %s), but not checked the box allowing cross-division games.', true), $team_names[$game['home_team']], $team_names[$game['away_team']]), 'class' => 'info');
  1236. } else {
  1237. // Make sure that the game slot selected is available to one of the teams
  1238. $available_to_home = Set::extract("/GameSlot[id={$game['game_slot_id']}]/..", $available_slots[$home_division]);
  1239. if (empty($available_to_home)) {
  1240. $available_to_away = Set::extract("/GameSlot[id={$game['game_slot_id']}]/..", $available_slots[$away_division]);
  1241. if (empty($available_to_away)) {
  1242. return array('text' => sprintf(__('You have scheduled a game between %s and %s in a game slot not available to them.', true), $home_name, $away_name), 'class' => 'info');
  1243. } else {
  1244. // Game is happening on a field only available to the away team, so make them the home team instead
  1245. $data['Game'][$key]['division_id'] = $away_division;
  1246. list($data['Game'][$key]['home_team'], $data['Game'][$key]['away_team']) = array($game['away_team'], $game['home_team']);
  1247. $field = $available_to_away[0]['Field'];
  1248. }
  1249. } else {
  1250. $field = $available_to_home[0]['Field'];
  1251. }
  1252. // At this point, we know that the home team has access to the game slot,
  1253. // so we will make the division id of the game match that team
  1254. $data['Game'][$key]['division_id'] = $home_division;
  1255. }
  1256. $home = ($teams && !empty($data['Game'][$key]['home_team']) ? $teams[$data['Game'][$key]['home_team']] : null);
  1257. $away = ($teams && !empty($data['Game'][$key]['away_team']) ? $teams[$data['Game'][$key]['away_team']] : null);
  1258. if (!$this->updateFieldRanking($data['Game'][$key], $field, $home, $away)) {
  1259. return array('text' => __('Failed to update field preference stats!', true), 'class' => 'warning');
  1260. }
  1261. }
  1262. $ret = $this->_saveGames($data['Game'], $publish);
  1263. if ($ret !== true) {
  1264. return $ret;
  1265. }
  1266. $available_slot_ids = array();
  1267. foreach ($available_slots as $slots) {
  1268. $available_slot_ids = array_merge($available_slot_ids, Set::extract ('/Game/game_slot_id', $slots));
  1269. }
  1270. $unused_slots = array_diff ($available_slot_ids, $used_slots);
  1271. if (empty($unused_slots) || $this->GameSlot->updateAll (array('assigned' => 0), array('GameSlot.id' => $unused_slots))) {
  1272. return true;
  1273. } else {
  1274. return array('text' => __('Saved schedule changes, but failed to clear unused slots!', true), 'class' => 'warning', 'result' => true);
  1275. }
  1276. }
  1277. }
  1278. ?>