Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Contest deletion #67

Merged
merged 3 commits into from
Sep 26, 2023
Merged
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
16 changes: 15 additions & 1 deletion i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,19 @@
"this-is-wscontest": "This is the Wikisource Contest tool",
"gpl-link": "GPL 3.0+",
"source-code": "Source code",
"issue-tracker": "Issue tracker"
"issue-tracker": "Issue tracker",

"deleted-successfully": "Contest $1 deleted successfully",
"proceed-caution": "Proceed with caution",
"irreversible-action": "This action is irreversible and all data related to the contest will be lost forever",
"delete-contest": "Delete contest",

"delete": "Delete",
"tip": "TIP",
"click-here-link": "Click here",
"save-scores-before-delete": "to save your scores as wikitext before deleting your contest",
"confirm-delete-text": "Please confirm that you want to delete",
"do-not-delete": "Don't delete",
"confirm-delete": "Yes, I confirm"

}
14 changes: 13 additions & 1 deletion i18n/qqq.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,17 @@
"this-is-wscontest": "Footer link to the tool homepage.",
"gpl-link": "Footer link to the GPL license.",
"source-code": "Footer link to the tool's source code.",
"issue-tracker": "Footer link to the tool's issue tracker."
"issue-tracker": "Footer link to the tool's issue tracker.",
"deleted-successfully": "Contest $1 deleted successfully",
"proceed-caution": "Text that warns the user to proceed with caution",
"irreversible-action": "Text that warns the user about an irreversible action",
"delete-contest": "Button text to delete a contest",

"delete": "Label for the modal title",
"tip": "Label for the tip",
"click-here-link": "Text displayed on the URL to retrieve scores as wikitext before deletion",
"save-scores-before-delete": "Text that prompts the user to save scores of the contest before deletion",
"confirm-delete-text": "Text that asks confirmation from the user for contest deletion",
"do-not-delete": "Button text that cancels the deletion process",
"confirm-delete": "Button text that confirms and initiates the deletion process"
}
41 changes: 41 additions & 0 deletions src/Controller/ContestsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ public function edit(
$indexPages = '';
$excludedUsers = '';
$admins = '';
$scoresExist = false;
if ( $id ) {
$contest = $contestRepository->get( $id );
$isAdmin = false;
Expand All @@ -149,6 +150,9 @@ public function edit(
foreach ( $contest['index_pages'] as $indexPage ) {
$indexPages .= $indexPage['url'] . "\n";
}
if ( count( $contestRepository->getscores( $id ) ) > 0 ) {
$scoresExist = true;
}
} else {
$now = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
$twoWeeks = new DateInterval( 'P14D' );
Expand All @@ -164,13 +168,50 @@ public function edit(

return $this->render( 'contests_edit.html.twig', [
'contest' => $contest,
'scoresExist' => $scoresExist,
'admins' => $admins,
'index_pages' => $indexPages,
'excluded_users' => $excludedUsers,
'score_calculation_interval' => $scoreCalculationInterval,
] );
}

/**
* phpcs:ignore MediaWiki.Commenting.FunctionAnnotations.UnrecognizedAnnotation
* @Route("/c/delete", name="contests_delete", methods={"POST"})
* @param Session $session
* @param ContestRepository $contestRepository
* @param Request $request
* @return Response
*/
public function delete( Session $session, ContestRepository $contestRepository, Request $request ): Response {
$username = $this->getLoggedInUsername( $session );
if ( !$username ) {
$this->addFlash( 'warning', [ 'not-logged-in', [] ] );
} elseif ( !$this->isCsrfTokenValid( 'contest-delete', $request->request->get( 'csrf_token' ) ) ) {
throw new AccessDeniedHttpException();
} else {
$contest = $contestRepository->get( $request->query->get( 'deletedId' ) );

// check if the user is an admin
$isAdmin = false;
foreach ( $contest['admins'] as $admin ) {
$isAdmin = $isAdmin || $admin['name'] === $username;
}
if ( !$isAdmin ) {
throw $this->createAccessDeniedException();
}

if ( $request->query->get( 'deletedId' ) ) {
// execute delete query
$contestRepository->deleteContest( $request->query->get( 'deletedId' ) );
$this->addFlash( 'success', [ 'deleted-successfully', [ $contest['name'] ] ] );
}
}

return $this->redirectToRoute( 'contests' );
}

/**
* phpcs:ignore MediaWiki.Commenting.FunctionAnnotations.UnrecognizedAnnotation
* @Route( "/c/save", name="contests_save" )
Expand Down
51 changes: 51 additions & 0 deletions src/Repository/ContestRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -302,4 +302,55 @@ private function getExcludedUsers( $contestId ): array {
public function deleteScores( $contestId ): void {
$this->db->executeStatement( 'DELETE FROM scores WHERE contest_id = :id', [ 'id' => $contestId ] );
}

/**
* @param string $contestId
* @return void
*/
public function deleteAdmins( $contestId ): void {
$this->db->executeStatement( 'DELETE FROM admins WHERE contest_id = :id', [ 'id' => $contestId ] );
}

/**
* @param string $contestId
* @return void
*/
public function deleteContestIndexPages( $contestId ): void {
$this->db->executeStatement( 'DELETE FROM contest_index_pages WHERE contest_id = :id', [ 'id' => $contestId ] );
}

/**
* @param string $contestId
* @return void
*/
public function deleteExcludedUsers( $contestId ): void {
$this->db->executeStatement( 'DELETE FROM excluded_users WHERE contest_id = :id', [ 'id' => $contestId ] );
}

/**
* @param string $contestId
* @return void
*/
public function deleteContest( $contestId ): void {
$this->db->beginTransaction();

/**
* NOTE: Data in `users` table and `index_pages` table
* is PRESERVED even if all other data about the contest
* is deleted.
*/

// delete tables containing foreign keys
$this->deleteAdmins( $contestId );
$this->deleteExcludedUsers( $contestId );
$this->deleteContestIndexPages( $contestId );
$this->deleteScores( $contestId );

// delete the contest
$sql2 = 'DELETE FROM contests WHERE id = :id';
$this->db->executeStatement( $sql2, [ 'id' => $contestId ] );

// perform a COMMIT on the database
$this->db->commit();
}
}
2 changes: 1 addition & 1 deletion templates/base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
{% for type, messages in app.flashes %}
<div class="flashes">
{% for message in messages %}
<div class="alert alert-warning alert-dismissible fade show">
<div class="alert alert-{{type}} alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
{{ msg(message.0, message.1) | raw }}
</div>
Expand Down
32 changes: 32 additions & 0 deletions templates/contests_edit.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,38 @@
{% endif %}
<span class="d-block text-muted">{{ msg('save-resets-scores', [(score_calculation_interval * 2)~'']) }}</span>
</p>
{% if contest.id %}
<div class="border border-danger p-3">
<h5 class="text-danger">{{ msg('proceed-caution') }}</h5>
<p class="text-muted">{{ msg('irreversible-action') }}</p>
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{{ msg('delete-contest') }}</button>
</div>
{% endif %}
</form>
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="deleteModalLabel">{{ msg('delete') }} {{contest.name}}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
{% if scoresExist == true %}
<p><span class='border border-2 border-dark fw-bold rounded px-1'>{{ msg('tip') }}</span>
<a href="{{ path('contests_view', {id: contest.id, format: 'wikitext'}) }}">{{ msg('click-here-link') }}</a>
{{ msg('save-scores-before-delete') }}</p>
{% endif %}
<p class="text-danger">{{ msg('confirm-delete-text') }} <span class="fw-bold">{{contest.name}}</span></p>
</div>
<div class="modal-footer">
<form action="{{ path('contests_delete', {deletedId: contest.id}) }}" method="post">
<button type="button" class="btn btn-light border" data-bs-dismiss="modal">{{ msg('do-not-delete') }}</button>
<input type="hidden" name="csrf_token" value="{{ csrf_token( 'contest-delete' ) }}"/>
<input type="submit" class="btn btn-danger" value="{{ msg('confirm-delete') }}" />
</form>
</div>
</div>
</div>
</div>

{% endblock %}