Skip to content

Commit

Permalink
Feature: Contest deletion
Browse files Browse the repository at this point in the history
Add the contest delete feature
that allows admins to delete
contests created by them

Bug: T197769
  • Loading branch information
Parthiv-M authored Sep 26, 2023
1 parent 454b18e commit d02b840
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 3 deletions.
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 %}

0 comments on commit d02b840

Please sign in to comment.