Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deploy/database/schema.game.sql
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ CREATE TABLE game (
last_winner_id SMALLINT UNSIGNED,
tournament_id SMALLINT UNSIGNED,
tournament_round_number SMALLINT UNSIGNED,
description VARCHAR(255) NOT NULL,
description VARCHAR(305) NOT NULL,
chat TEXT,
previous_game_id MEDIUMINT UNSIGNED,
FOREIGN KEY (previous_game_id) REFERENCES game(id)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE game MODIFY description VARCHAR(305) NOT NULL;
19 changes: 19 additions & 0 deletions src/api/ApiResponder.php
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,25 @@ protected function get_interface_response_unfollowTournament($interface, $args)
return $retval;
}

/**
* Interface redirect for changeTournamentDesc
*
* @param type $interface
* @param type $args
* @return type
*/
protected function get_interface_response_changeTournamentDesc($interface, $args) {
$retval = $interface->tournament()->change_tournament_desc(
$this->session_user_id(),
$args['tournamentId'],
$args['description']
);
if (isset($retval)) {
$interface->player()->update_last_action_time($this->session_user_id());
}
return $retval;
}

// End of tournament-related methods
////////////////////////////////////////////////////////////

Expand Down
10 changes: 10 additions & 0 deletions src/api/ApiSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ class ApiSpec {
),
'permitted' => array(),
),
'changeTournamentDesc' => array(
'mandatory' => array(
'tournamentId' => 'number',
'description' => array(
'arg_type' => 'string',
'maxlength' => self::TOURNAMENT_DESCRIPTION_MAX_LENGTH,
),
),
'permitted' => array(),
),
// countPendingGames returns:
// count: int,
'countPendingGames' => array(
Expand Down
14 changes: 14 additions & 0 deletions src/api/DummyApiResponder.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,20 @@ protected function get_interface_response_loadGameData($args) {
);
}

protected function get_interface_response_loadTournaments($args) {
return $this->load_json_data_from_file(
'loadTournaments',
'noargs.json'
);
}

protected function get_interface_response_loadTournamentData($args) {
return $this->load_json_data_from_file(
'loadTournamentData',
$args['tournament'] . '.json'
);
}

protected function get_interface_response_countPendingGames() {
return $this->load_json_data_from_file(
'countPendingGames',
Expand Down
119 changes: 114 additions & 5 deletions src/engine/BMInterfaceTournament.php
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,57 @@ protected function load_tournament_participants(BMTournament $tournament) {
$tournament->remainCountArray = $remainCountArray;
}

/**
* Set tournament description in database
*
* @param type $tournamentId
* @param type $tournamentDesc
*/
protected function set_tournament_description($tournamentId, $tournamentDesc) {
try {
$query = 'UPDATE tournament ' .
'SET description = :description ' .
'WHERE id = :id';
$parameters = array(
':description' => $tournamentDesc,
':id' => $tournamentId
);

self::$db->update($query, $parameters);

return $tournamentId;
} catch (BMExceptionDatabase $e) {
$this->set_message('Cannot set tournament description because the tournament ID was not valid');
return NULL;
} catch (Exception $e) {
$this->set_message('Tournament description set failed: ' . $e->getMessage());
error_log(
'Caught exception in BMInterface::set_tournament_description: ' .
$e->getMessage()
);
return NULL;
}
}

protected function is_tournament_creator($playerId, $tournamentId) {
if (($playerId <= 0) || ($tournamentId <= 0)) {
return FALSE;
}

$query = 'SELECT t.creator_id ' .
'FROM tournament AS t ' .
'WHERE t.id = :tournament_id;';
$parameters = array(
':tournament_id' => $tournamentId,
);
$columnReturnTypes = array(
'creator_id' => 'int',
);
$rows = self::$db->select_rows($query, $parameters, $columnReturnTypes);
$row = $rows[0];
return ($row['creator_id'] === $playerId);
}

/**
* Check whether a player is in a tournament
*
Expand All @@ -447,7 +498,7 @@ protected function is_player_in_tournament($playerId, $tournamentId) {
'AND t.tournament_id = :tournament_id;';
$parameters = array(
':player_id' => $playerId,
':tournament_id' => $tournamentId
':tournament_id' => $tournamentId,
);
$columnReturnTypes = array(
'player_id' => 'int',
Expand Down Expand Up @@ -503,17 +554,21 @@ protected function generate_new_games(BMTournament $tournament) {
array($gameData['buttonId1'], $gameData['buttonId2'])
);

$description = 'Round ' . $gameData['roundNumber'];
if ('' != $tournament->description) {
$description = $tournament->description . ' ' . $description;
$roundDescription = 'Tournament Round ' . $gameData['roundNumber'];
$tournDescription = $tournament->description;

if ('' == trim($tournDescription)) {
$tournDescription = $roundDescription;
} else {
$tournDescription = $tournDescription . ' • ' . $roundDescription;
}

$interfaceResponse = $this->game()->create_game_from_button_ids(
array($gameData['playerId1'], $gameData['playerId2']),
array($gameData['buttonId1'], $gameData['buttonId2']),
$buttonNames,
$tournament->gameMaxWins,
$description,
$tournDescription,
NULL,
0, // needs to be non-null, but also a non-player ID
TRUE,
Expand Down Expand Up @@ -609,6 +664,19 @@ protected function get_tournament_status(BMTournament $tournament) {
return $status;
}

/**
* Check whether a tournament is open to join
*
* @param int $tournamentId
* @return bool
*/
protected function is_tournament_open($tournamentId) {
$tournament = $this->load_tournament($tournamentId);
$tournamentState = $tournament->tournamentState;

return ($tournamentState <= BMTournamentState::JOIN_TOURNAMENT);
}

/**
* Check whether a tournament has ended, either through completion or cancellation
*
Expand Down Expand Up @@ -1250,4 +1318,45 @@ public function dismiss_tournament($playerId, $tournamentId) {
return NULL;
}
}

/**
* Change tournament description
*
* This changes the tournament description.
*
* It is only accessible to the tournament creator before the tournament has started.
*
* @param int $playerId
* @param int $tournamentId
* @param string $tournamentDesc
* @return bool|null
*/
public function change_tournament_desc($playerId, $tournamentId, $tournamentDesc) {
try {
if (!$this->is_tournament_creator($playerId, $tournamentId)) {
$this->set_message('Only tournament creators can change the description');
return NULL;
}

if (!$this->is_tournament_open($tournamentId)) {
$this->set_message("Tournament $tournamentId has already started");
return NULL;
}

$this->set_tournament_description($tournamentId, $tournamentDesc);

$this->set_message('Tournament description saved');
return TRUE;
} catch (BMExceptionDatabase $e) {
$this->set_message('Cannot change tournament description because tournament ID was not valid');
return NULL;
} catch (Exception $e) {
error_log(
'Caught exception in BMInterface::change_tournament_desc: ' .
$e->getMessage()
);
$this->set_message('Internal error while changing a tournament description');
return NULL;
}
}
}
128 changes: 128 additions & 0 deletions src/ui/js/Env.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ Env.applyBbCodeToHtml = function(htmlToParse) {

var tagName;

htmlToParse = htmlToParse.replace('\n', '<br>');

while (htmlToParse) {
var currentPattern = allStartTagsPattern;
if (tagStack.length !== 0) {
Expand Down Expand Up @@ -524,6 +526,132 @@ Env.applyBbCodeToHtml = function(htmlToParse) {
return outputHtml;
};

Env.removeBbCodeFromHtml = function(htmlToParse) {
// This is all rather more complicated than one might expect, but any attempt
// to parse BB code using simple regular expressions rather than tokenization
// is in the same family as parsing HTML with regular expressions, which
// summons Zalgo.
// (See: http://stackoverflow.com/
// questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags)

var replacements = {
'b': {},
'i': {},
'u': {},
's': {},
'code': {},
'spoiler': {},
'quote': {},
'game': {
'isAtomic': true,
},
'player': {
'isAtomic': true,
},
'button': {
'isAtomic': true,
},
'set': {
'isAtomic': true,
},
'tourn': {
'isAtomic': true,
},
'wiki': {
'isAtomic': true,
},
'issue': {
'isAtomic': true,
},
'forum': {},
'[': {
'isAtomic': true,
},
};

var outputHtml = '';
var tagStack = [];

// We want to build a pattern that we can use to identify any single
// BB code start tag
var allStartTagsPattern = '';
$.each(replacements, function(tagName) {
if (allStartTagsPattern !== '') {
allStartTagsPattern += '|';
}
// Matches, e.g., '[ b ]' or '[game = "123"]'
// The (?:... part means that we want parentheses around the whole
// thing (so we we can OR it together with other ones), but we don't
// want to capture the value of the whole thing as a group
allStartTagsPattern +=
'(?:\\[(' + Env.escapeRegexp(tagName) + ')(?:=([^\\]]*?))?])';
});

var tagName;

htmlToParse = htmlToParse.replace('\n', ' ');

while (htmlToParse) {
var currentPattern = allStartTagsPattern;
if (tagStack.length !== 0) {
// The tag that was most recently opened
tagName = tagStack[tagStack.length - 1];
// Matches '[/i]' et al.
// (so that we can spot the end of the current tag as well)
currentPattern +=
'|(?:\\[(/' + Env.escapeRegexp(tagName) + ')])';
}
// The first group should be non-greedy (hence the ?), and the last one
// should be greedy, so that nested tags work right
// (E.g., in '...blah[/quote] blah [/quote] blah', we want the first .*
// to end at the first [/quote], not the second)
currentPattern = '^(.*?)(?:' + currentPattern + ')(.*)$';
// case-insensitive, multi-line
var regExp = new RegExp(currentPattern, 'im');

var match = htmlToParse.match(regExp);
if (match) {
var stuffBeforeTag = match[1];
// javascript apparently believes that capture groups that don't
// match anything are just important as those that do. So we need
// to do some acrobatics to find the ones we actually care about.
// (match[0] is the whole matched string; match[1] is the stuff before
// the tag. So we start with match[2].)
tagName = '';
for (var i = 2; i < match.length; i++) {
tagName = match[i];
if (tagName) {
break;
}
}
tagName = tagName.toLowerCase();
var stuffAfterTag = match[match.length - 1];

outputHtml += stuffBeforeTag;
if (tagName.substring(0, 1) === '/') {
// If we've found our closing tag, we can finish the current tag and
// pop it off the stack
tagName = tagStack.pop();
} else {
if (!replacements[tagName].isAtomic) {
// If there's a closing tag coming along later, push this tag
// on the stack so we'll know we're waiting on it
tagStack.push(tagName);
}
}

htmlToParse = stuffAfterTag;
} else {
// If we don't find any more BB code tags that we're interested in,
// then we must have reached the end
outputHtml += htmlToParse;
htmlToParse = '';
}
}

return outputHtml;
};

Env.escapeRegexp = function(str) {
return str.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
};
Expand Down
2 changes: 1 addition & 1 deletion src/ui/js/Game.js
Original file line number Diff line number Diff line change
Expand Up @@ -1836,7 +1836,7 @@ Game.pageAddGameHeader = function(action_desc) {

if (Api.game.description) {
Game.page.append($('<div>', {
'text': Api.game.description,
'html': Env.applyBbCodeToHtml(Api.game.description),
'class': 'gameDescDisplay',
}));
}
Expand Down
8 changes: 5 additions & 3 deletions src/ui/js/Overview.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,11 @@ Overview.addScoreCol = function(gameRow, gameInfo) {

Overview.addDescCol = function(gameRow, description) {
var descText = '';
if (typeof(description) == 'string') {
descText = description.substring(0, 30) +
((description.length > 30) ? '...' : '');
if (typeof(description) === 'string') {
var descriptionNoMarkup = Env.removeBbCodeFromHtml(description);

descText = descriptionNoMarkup.substring(0, 30) +
((descriptionNoMarkup.length > 30) ? '...' : '');
}
gameRow.append($('<td>', {
'class': 'gameDescDisplay',
Expand Down
Loading