/**
  * Merges the records for two players, retaining the id of the destination player
  * @param int $source_player_id
  * @param int $destination_player_id
  * @return void
  */
 public function MergePlayers(Player $source_player, Player $destination_player)
 {
     if (!$source_player->GetId()) {
         throw new Exception("source_player must have an Id");
     }
     if (!$destination_player->GetId()) {
         throw new Exception("destination_player must have an Id");
     }
     if ($source_player->GetPlayerRole() != PLAYER::PLAYER) {
         throw new Exception("Cannot merge source player because it's an extras player");
     }
     if ($destination_player->GetPlayerRole() != PLAYER::PLAYER) {
         throw new Exception("Cannot merge destination player because it's an extras player");
     }
     $players = $this->GetSettings()->GetTable("Player");
     $batting = $this->GetSettings()->GetTable("Batting");
     $bowling = $this->GetSettings()->GetTable("Bowling");
     $matches = $this->GetSettings()->GetTable("Match");
     $statistics = $this->GetSettings()->GetTable("PlayerMatch");
     # Make a note of matches where source player was involved
     $sql = "SELECT DISTINCT match_team_id FROM {$batting} WHERE player_id = " . $source_player->GetId();
     $result = $this->GetDataConnection()->query($sql);
     $source_batted = array();
     while ($row = $result->fetch()) {
         $source_batted[] = $row->match_team_id;
     }
     $sql = "SELECT match_team_id FROM {$statistics}\n\t\t\t\tWHERE player_id = " . $source_player->GetId() . " AND (run_outs > 0 OR catches > 0)";
     $result = $this->GetDataConnection()->query($sql);
     $source_fielded = array();
     while ($row = $result->fetch()) {
         $source_fielded[] = $row->match_team_id;
     }
     $sql = "SELECT match_team_id FROM {$statistics}\n\t\t\t\tWHERE player_id = " . $source_player->GetId() . " AND wickets IS NOT NULL";
     $result = $this->GetDataConnection()->query($sql);
     $source_bowled = array();
     while ($row = $result->fetch()) {
         $source_bowled[] = $row->match_team_id;
     }
     $sql = "SELECT match_id FROM {$statistics}\n\t\t\t\tWHERE player_id = " . $source_player->GetId() . " AND player_of_match = 1";
     $result = $this->GetDataConnection()->query($sql);
     $source_player_of_match = array();
     while ($row = $result->fetch()) {
         $source_player_of_match[] = $row->match_id;
     }
     # Transfer batting and bowling
     $this->LoggedQuery("UPDATE {$batting} SET player_id = " . Sql::ProtectNumeric($destination_player->GetId()) . "\n\t\tWHERE player_id = " . Sql::ProtectNumeric($source_player->GetId()));
     $this->LoggedQuery("UPDATE {$batting} SET dismissed_by_id = " . Sql::ProtectNumeric($destination_player->GetId()) . "\n\t\tWHERE dismissed_by_id = " . Sql::ProtectNumeric($source_player->GetId()));
     $this->LoggedQuery("UPDATE {$batting} SET bowler_id = " . Sql::ProtectNumeric($destination_player->GetId()) . "\n\t\tWHERE bowler_id = " . Sql::ProtectNumeric($source_player->GetId()));
     $this->LoggedQuery("UPDATE {$bowling} SET player_id = " . Sql::ProtectNumeric($destination_player->GetId()) . "\n\t\tWHERE player_id = " . Sql::ProtectNumeric($source_player->GetId()));
     # Update dismissals in stats table too, because then fielding statistics will update correctly below.
     # Normally dismissals are updated with the batting, but here it's quite possible we are only updating the fielding.
     $this->LoggedQuery("UPDATE {$statistics} SET caught_by = " . Sql::ProtectNumeric($destination_player->GetId()) . "\n\t\tWHERE caught_by = " . Sql::ProtectNumeric($source_player->GetId()));
     $this->LoggedQuery("UPDATE {$statistics} SET run_out_by = " . Sql::ProtectNumeric($destination_player->GetId()) . "\n\t\tWHERE run_out_by = " . Sql::ProtectNumeric($source_player->GetId()));
     if (!$this->is_internal_delete) {
         # Doing an internal delete the destination player will be Unknown. Transfer batting and bowling
         # because that preserves the position for other bowlers and batters as well as related statistics
         # such as number of runs. But set player of the match to null because there's not much value in
         # setting it unknown.
         # Transfer player of the match award
         $this->LoggedQuery("UPDATE {$matches} SET player_of_match_id = " . Sql::ProtectNumeric($destination_player->GetId()) . ",\n\t\t\tdate_changed = " . gmdate('U') . "\n\t\t\tWHERE player_of_match_id = " . Sql::ProtectNumeric($source_player->GetId()));
         $this->LoggedQuery("UPDATE {$matches} SET player_of_match_home_id = " . Sql::ProtectNumeric($destination_player->GetId()) . ",\n\t\t\tdate_changed = " . gmdate('U') . "\n\t\t\tWHERE player_of_match_home_id = " . Sql::ProtectNumeric($source_player->GetId()));
         $this->LoggedQuery("UPDATE {$matches} SET player_of_match_away_id = " . Sql::ProtectNumeric($destination_player->GetId()) . ",\n\t\t\tdate_changed = " . gmdate('U') . "\n\t\t\tWHERE player_of_match_away_id = " . Sql::ProtectNumeric($source_player->GetId()));
         # If a user has claimed either player, remember that. If two different claimants, prefer the destination one.
         if ($source_player->GetUser() instanceof User and $source_player->GetUser()->GetId()) {
             $this->LoggedQuery("UPDATE {$players}\n\t\t\t\tSET user_id = " . Sql::ProtectNumeric($source_player->GetUser()->GetId()) . ",\n\t\t\t\tdate_changed = " . gmdate('U') . "\n\t\t\t\tWHERE player_id = " . Sql::ProtectNumeric($destination_player->GetId()) . " AND user_id = NULL");
         }
         # Now that the source player's data has been moved, delete the source player
         $this->Delete(array($source_player->GetId()));
     }
     # Recalculate all the derived data
     # Note: this method is tightly integrated with the Delete() method. They call each other. When the source player
     # is deleted, above, it will call back into this method before all the derived statistics for the source player
     # have gone. Therefore the queries at the top of this method will find the source player still exists. That in turn
     # leads to these methods being called for a player which has only derived statistics, and no actual data. It is
     # important therefore to call DeleteObsoleteStatistics() to clear out the redundant records as soon as they created.
     require_once 'stoolball/statistics/statistics-manager.class.php';
     $statistics_manager = new StatisticsManager($this->GetSettings(), $this->GetDataConnection());
     if (count($source_batted)) {
         $statistics_manager->UpdateBattingStatistics(array($destination_player->GetId()), $source_batted);
     }
     if (count($source_fielded)) {
         $statistics_manager->UpdateFieldingStatistics(array($destination_player->GetId()), $source_fielded);
     }
     if (count($source_bowled)) {
         $statistics_manager->UpdateBowlingStatistics(array($destination_player->GetId()), $source_bowled);
     }
     foreach ($source_player_of_match as $match_id) {
         $statistics_manager->UpdatePlayerOfTheMatchStatistics($match_id);
         $statistics_manager->DeleteObsoleteStatistics($match_id);
     }
     $statistics_manager->UpdatePlayerStatistics(array($destination_player->GetId()));
     unset($statistics_manager);
 }
 /**
  * @access public
  * @return void
  * @param int[] $a_ids
  * @desc Delete from the db the Matches matching the supplied ids.
  */
 public function DeleteMatch($a_ids)
 {
     # check parameter
     if (!is_array($a_ids)) {
         die('No matches to delete');
     }
     # build query
     $delete_sql = array();
     $s_match = $this->GetSettings()->GetTable('Match');
     $s_season_match = $this->GetSettings()->GetTable('SeasonMatch');
     $s_mt = $this->GetSettings()->GetTable('MatchTeam');
     $batting = $this->GetSettings()->GetTable('Batting');
     $bowling = $this->GetSettings()->GetTable('Bowling');
     $stats = $this->GetSettings()->GetTable('PlayerMatch');
     $s_ids = join(', ', $a_ids);
     # delete batting and bowling
     $match_team_ids = array();
     $result = $this->GetDataConnection()->query("SELECT match_team_id FROM {$s_mt} WHERE match_id IN ({$s_ids})");
     while ($row = $result->fetch()) {
         $match_team_ids[] = $row->match_team_id;
     }
     $result->closeCursor();
     if (count($match_team_ids)) {
         $match_team_ids = join(",", $match_team_ids);
         $delete_sql[] = "DELETE FROM {$batting} WHERE match_team_id IN ({$match_team_ids})";
         $delete_sql[] = "DELETE FROM {$bowling} WHERE match_team_id IN ({$match_team_ids})";
     }
     $this->GetDataConnection()->query("DELETE FROM {$stats} WHERE match_id IN ({$s_ids})");
     # delete teams
     $delete_sql[] = "DELETE FROM {$s_mt} WHERE match_id IN ({$s_ids})";
     # delete seasons
     $delete_sql[] = "DELETE FROM {$s_season_match} WHERE match_id IN ({$s_ids})";
     # if this is a tournament, delete the matches
     $tournament_match_ids = array();
     $s_sql = 'SELECT match_id FROM ' . $s_match . ' WHERE tournament_match_id IN (' . $s_ids . ') ';
     $result = $this->GetDataConnection()->query($s_sql);
     while ($row = $result->fetch()) {
         $tournament_match_ids[] = $row->match_id;
     }
     $result->closeCursor();
     if (count($tournament_match_ids)) {
         $this->DeleteMatch($tournament_match_ids);
     }
     # delete comments thread
     $delete_sql[] = "DELETE FROM nsa_forum_message WHERE item_id IN ({$s_ids}) AND item_type = " . ContentType::STOOLBALL_MATCH;
     # delete match(es)
     $delete_sql[] = "DELETE FROM {$s_match} WHERE match_id IN ({$s_ids});";
     # delete from short URL cache
     require_once 'http/short-url-manager.class.php';
     $o_url_manager = new ShortUrlManager($this->GetSettings(), $this->GetDataConnection());
     $s_sql = "SELECT short_url FROM {$s_match} WHERE match_id IN ({$s_ids})";
     $result = $this->GetDataConnection()->query($s_sql);
     while ($row = $result->fetch()) {
         $o_url_manager->Delete($row->short_url);
     }
     $result->closeCursor();
     unset($o_url_manager);
     # get players involved in the match before it's deleted, so that player statistics can be updated
     require_once 'stoolball/player-manager.class.php';
     $player_manager = new PlayerManager($this->GetSettings(), $this->GetDataConnection());
     $player_ids = $player_manager->ReadPlayersInMatch($a_ids);
     unset($player_manager);
     # Run the collected delete commands
     foreach ($delete_sql as $sql) {
         $this->LoggedQuery($sql);
     }
     # update player stats, removing this match and any players who featured only in this match
     require_once 'stoolball/statistics/statistics-manager.class.php';
     $statistics_manager = new StatisticsManager($this->GetSettings(), $this->GetDataConnection());
     if (count($player_ids)) {
         $statistics_manager->UpdatePlayerStatistics($player_ids);
     }
     unset($statistics_manager);
     return $this->GetDataConnection()->GetAffectedRows();
 }