Commit 159e12c3 authored by Andreas Heimann's avatar Andreas Heimann

add latest stuff

parent 47bb82b6
.section-song-delete {
display: flex;
justify-content: center;
align-items: center;
padding: 50px;
}
.box {
width: 600px;
background: rgba(255, 255, 255, 0.1);
display: grid;
grid-template-rows: 1fr auto;
border-radius: 6px;
overflow: hidden;
}
.box .text {
padding: 25px;
display: grid;
grid-template-rows: auto 1fr auto;
grid-gap: 15px;
}
.box .text .title {
letter-spacing: 0.25em;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
}
.box .text p {
margin: 0px;
line-height: 1.5em;
}
.box .song-item {
width: 173px;
margin: 0 auto;
margin-bottom: 25px;
}
.box .actions {
padding: 25px;
background: rgba(0, 0, 0, 0.1);
display: flex;
justify-content: flex-end;
}
.box .actions .button {
margin-left: 15px;
}
.box .actions .button.button-primary {
background: #fff;
color: #222;
}
.box .actions .button.button-primary:hover {
opacity: 0.75;
}
.section-song-delete {
display: flex;
justify-content: center;
align-items: center;
padding: 50px;
}
.box {
width: 600px;
background: rgba(255,255,255,0.1);
display: grid;
grid-template-rows: 1fr auto;
border-radius: 6px;
overflow: hidden;
& .text {
padding: 25px;
display: grid;
grid-template-rows: auto 1fr auto;
grid-gap: 15px;
& .title {
letter-spacing: 0.25em;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
}
& p {
margin: 0px;
line-height: 1.5em;
}
}
& .song-item {
width: 173px;
margin: 0 auto;
margin-bottom: 25px;
}
& .actions {
padding: 25px;
background: rgba(0,0,0,0.1);
display: flex;
justify-content: flex-end;
& .button {
margin-left: 15px;
&.button-primary {
background: #fff;
color: #222;
&:hover {
opacity: 0.75;
}
}
}
}
}
\ No newline at end of file
......@@ -56,6 +56,7 @@
margin-right: 10px;
margin-top: 5px;
word-break: break-all;
text-decoration: none;
transition: 0.2s ease-in-out all;
}
.section-song-detail .song-detail-background .song-detail-dim .song-detail .song-meta-data .song-tags .tag:hover {
......@@ -85,32 +86,64 @@
text-decoration: none;
padding-right: 15px;
}
.section-song-detail .song-detail-actions {
.section-song-detail .song-detail-actions,
.section-song-detail .song-detail-actions-owner,
.section-song-detail .song-detail-actions-moderator {
padding: 50px;
padding-top: 0px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 25px;
}
.section-song-detail .song-detail-actions .button {
.section-song-detail .song-detail-actions.song-detail-actions-owner,
.section-song-detail .song-detail-actions-owner.song-detail-actions-owner,
.section-song-detail .song-detail-actions-moderator.song-detail-actions-owner {
grid-template-columns: 1fr 1fr 1fr 1fr 60px 60px;
}
.section-song-detail .song-detail-actions.song-detail-actions-moderator,
.section-song-detail .song-detail-actions-owner.song-detail-actions-moderator,
.section-song-detail .song-detail-actions-moderator.song-detail-actions-moderator {
grid-template-columns: 1fr 1fr 1fr 1fr 60px;
}
.section-song-detail .song-detail-actions .button,
.section-song-detail .song-detail-actions-owner .button,
.section-song-detail .song-detail-actions-moderator .button {
padding: 15px 0px;
font-size: 16px;
transition: 0.2s ease-in-out all, 0.1s ease-in-out transform;
}
.section-song-detail .song-detail-actions .button.button-primary {
.section-song-detail .song-detail-actions .button.button-primary,
.section-song-detail .song-detail-actions-owner .button.button-primary,
.section-song-detail .song-detail-actions-moderator .button.button-primary {
background: #fff;
color: #222;
}
.section-song-detail .song-detail-actions .button.button-primary:hover {
.section-song-detail .song-detail-actions .button.button-primary:hover,
.section-song-detail .song-detail-actions-owner .button.button-primary:hover,
.section-song-detail .song-detail-actions-moderator .button.button-primary:hover {
background: #fff;
color: #222;
}
.section-song-detail .song-detail-actions .button:hover {
.section-song-detail .song-detail-actions .button.button-icon,
.section-song-detail .song-detail-actions-owner .button.button-icon,
.section-song-detail .song-detail-actions-moderator .button.button-icon {
padding: 11px 0px;
}
.section-song-detail .song-detail-actions .button.button-icon i,
.section-song-detail .song-detail-actions-owner .button.button-icon i,
.section-song-detail .song-detail-actions-moderator .button.button-icon i {
font-size: 20px;
}
.section-song-detail .song-detail-actions .button:hover,
.section-song-detail .song-detail-actions-owner .button:hover,
.section-song-detail .song-detail-actions-moderator .button:hover {
background: rgba(255, 255, 255, 0.2);
color: #fff;
opacity: 0.6;
transform: translateY(-4px);
}
.section-song-detail .song-detail-actions .button:active {
.section-song-detail .song-detail-actions .button:active,
.section-song-detail .song-detail-actions-owner .button:active,
.section-song-detail .song-detail-actions-moderator .button:active {
transform: translateY(-2px);
}
......@@ -58,6 +58,7 @@
margin-right: 10px;
margin-top: 5px;
word-break: break-all;
text-decoration: none;
transition: 0.2s ease-in-out all;
&:hover {
......@@ -96,13 +97,21 @@
}
}
}
& .song-detail-actions {
& .song-detail-actions, & .song-detail-actions-owner, & .song-detail-actions-moderator {
padding: 50px;
padding-top: 0px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 25px;
&.song-detail-actions-owner {
grid-template-columns: 1fr 1fr 1fr 1fr 60px 60px;
}
&.song-detail-actions-moderator {
grid-template-columns: 1fr 1fr 1fr 1fr 60px;
}
& .button {
padding: 15px 0px;
font-size: 16px;
......@@ -118,6 +127,14 @@
}
}
&.button-icon {
padding: 11px 0px;
& i {
font-size: 20px;
}
}
&:hover {
background: rgba(255,255,255,0.2);
color: #fff;
......
......@@ -292,6 +292,47 @@ class ModerationController extends AbstractController
return $this->redirectToRoute('moderation.reports.song', array('reportId' => $reportId));
}
/**
* @Route("/moderation/song/{songId}/remove", name="moderation.song.remove")
*/
public function songRemove(Request $request, int $songId)
{
$em = $this->getDoctrine()->getManager();
$data = [];
$songToRemove = $em->getRepository(Song::class)->findOneBy(array('id' => $songId));
$uploaderId = $songToRemove->getUploader();
// Remove .srtb File
try {
@unlink($this->getParameter('srtb_path').DIRECTORY_SEPARATOR.$songToRemove->getFileReference().".srtb");
} catch(FileNotFoundException $e) {
}
// Remove cover
try {
@unlink($this->getParameter('cover_path').DIRECTORY_SEPARATOR.$songToRemove->getFileReference().".png");
} catch(FileNotFoundException $e) {
}
// Remove .ogg files
try {
$oggFiles = glob($this->getParameter('audio_path').DIRECTORY_SEPARATOR.$songToRemove->getFileReference()."_*.ogg");
foreach($oggFiles as $oggFile) {
@unlink($oggFile);
}
} catch(FileNotFoundException $e) {
}
$em->remove($songToRemove);
$em->flush();
return $this->redirectToRoute('user.detail', array('userId' => $uploaderId));
}
/**
* @Route("/moderation/reports/song/{reportId}/remove", name="moderation.reports.song.remove")
*/
......
......@@ -95,6 +95,7 @@ class ReportController extends AbstractController
'This song contains my intellectual property (DMCA Takedown)' => 'dmca',
'This song is broken' => 'broken',
'This song is spam' => 'spam',
'This song has wrong meta data' => 'metadata',
'Other reason' => 'other',
]])
->add('reportText', TextareaType::class, ['label' => 'Explain the situation', 'row_attr' => array('class' => "tags-field"), 'attr' => array('rows' => 5)])
......
......@@ -7,9 +7,16 @@ use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use App\Entity\Song;
use App\Utils\HelperFunctions;
use App\Entity\User;
class SongController extends AbstractController
......@@ -87,4 +94,267 @@ class SongController extends AbstractController
return $response;
}
}
/**
* @Route("/song/{songId}/update", name="song.update")
*/
public function songUpdate(Request $request, UserInterface $user, int $songId)
{
$em = $this->getDoctrine()->getManager();
$data = [];
$song = $em->getRepository(Song::class)->findOneBy(array('id' => $songId, 'uploader' => $user->getId()));
if(!$song) {
throw new NotFoundHttpException();
} else {
$form = $this->createFormBuilder()
->add('backupPath', FileType::class, ['label' => 'Backup .zip', 'row_attr' => array('class' => 'upload-field'), 'attr' => array('accept' => '.zip')])
->add('tags', TextType::class, ['label' => 'Tags', 'row_attr' => array('class' => 'tags-field'), 'required' => false, 'data' => $song->getTags()])
->add('isExplicit', CheckboxType::class, ['label' => 'Is the song explicit?', 'row_attr' => array('class' => "tags-field"), 'required' => false, 'data' => $song->getIsExplicit()])
->add('save', SubmitType::class, ['label' => 'Upload'])
->getForm();
$form->handleRequest($request);
$tempVars['uploadForm'] = $form->createView();
if($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$backupFile = $data['backupPath'];
$song->setTags($data['tags']);
$song->setIsExplicit($data['isExplicit']);
if($backupFile) {
$zip = new \ZipArchive;
if($zip->open($backupFile)) {
try {
// extract backup
$extractionPath = $this->getParameter('temp_path').DIRECTORY_SEPARATOR.$song->getFileReference();
$zip->extractTo($extractionPath);
$zip->close();
// clean up old song data
// Remove .srtb File
try {
@unlink($this->getParameter('srtb_path').DIRECTORY_SEPARATOR.$song->getFileReference().".srtb");
} catch(FileNotFoundException $e) {
}
// Remove cover
try {
@unlink($this->getParameter('cover_path').DIRECTORY_SEPARATOR.$song->getFileReference().".png");
} catch(FileNotFoundException $e) {
}
// Remove .ogg files
try {
$oggFiles = glob($this->getParameter('audio_path').DIRECTORY_SEPARATOR.$song->getFileReference()."_*.ogg");
foreach($oggFiles as $oggFile) {
@unlink($oggFile);
}
} catch(FileNotFoundException $e) {
}
try {
try {
// get backup data
$srtbFiles = glob($extractionPath.DIRECTORY_SEPARATOR."*.srtb");
$srtbContent = json_decode(file_get_contents($srtbFiles[0]));
$trackInfo = null;
$clipInfo = [];
$trackData = [];
foreach($srtbContent->largeStringValuesContainer->values as $valueItem) {
if(strpos($valueItem->key, "TrackInfo") !== false) {
$trackInfo = json_decode($valueItem->val);
}
if(strpos($valueItem->key, "ClipInfo") !== false) {
$i = str_replace("SO_ClipInfo_ClipInfo_", "", $valueItem->key);
$clipInfo[$i] = json_decode($valueItem->val);
}
if(strpos($valueItem->key, "TrackData") !== false) {
$i = str_replace("SO_TrackData_TrackData_", "", $valueItem->key);
$trackData[$i] = json_decode($valueItem->val);
}
}
// set meta data
$song->setUploader($user->getId());
$song->setTitle($trackInfo->title);
$song->setSubtitle($trackInfo->subtitle);
$song->setArtist($trackInfo->artistName);
$song->setCharter($trackInfo->charter);
$song->setHasEasyDifficulty(false);
$song->setHasNormalDifficulty(false);
$song->setHasHardDifficulty(false);
$song->setHasExtremeDifficulty(false);
$song->setHasXDDifficulty(false);
foreach($trackData as $oneData) {
switch($oneData->difficultyType) {
case 2:
$song->setHasEasyDifficulty(true);
break;
case 3:
$song->setHasNormalDifficulty(true);
break;
case 4:
$song->setHasHardDifficulty(true);
break;
case 5:
$song->setHasExtremeDifficulty(true);
break;
case 6:
$song->setHasXDDifficulty(true);
break;
}
}
} catch(Exception $e) {
var_dump($e);
// clean up temp files
$hf = new HelperFunctions();
$hf->delTree($extractionPath);
}
try {
// find cover
$coverFiles = glob($extractionPath.DIRECTORY_SEPARATOR."AlbumArt".DIRECTORY_SEPARATOR.$trackInfo->albumArtReference->assetName.".*");
if($coverFiles[0]) {
$fileType = explode(".", $coverFiles[0])[count(explode(".", $coverFiles[0])) - 1];
if(in_array($fileType, array('jpg', 'png'))) {
$trackInfo->albumArtReference->assetName = $song->getFileReference();
rename($coverFiles[0], $this->getParameter('cover_path').DIRECTORY_SEPARATOR.$song->getFileReference().".png");
}
}
} catch(Exception $e) {
var_dump($e);
// clean up temp files
$hf = new HelperFunctions();
$hf->delTree($extractionPath);
}
try {
foreach($clipInfo as $clipIndex => $clipItem) {
// find audio
$assetName = $clipItem->clipAssetReference->assetName;
$newAssetName = $song->getFileReference()."_".$clipIndex;
$oggLocation = $extractionPath.DIRECTORY_SEPARATOR."AudioClips".DIRECTORY_SEPARATOR.$assetName.".ogg";
if(is_file($oggLocation)) {
$clipInfo[$clipIndex]->clipAssetReference->assetName = $newAssetName;
rename($oggLocation, $this->getParameter('audio_path').DIRECTORY_SEPARATOR.$newAssetName.".ogg");
}
}
} catch(Exception $e) {
var_dump($e);
// clean up temp files
$hf = new HelperFunctions();
$hf->delTree($extractionPath);
}
// write new track/clip info
foreach($srtbContent->largeStringValuesContainer->values as $valueItem) {
if(strpos($valueItem->key, "TrackInfo") !== false) {
$valueItem->val = json_encode($trackInfo);
}
if(strpos($valueItem->key, "ClipInfo") !== false) {
$i = str_replace("SO_ClipInfo_ClipInfo_", "", $valueItem->key);
$valueItem->val = json_encode($clipInfo[$i]);
}
if(strpos($valueItem->key, "TrackData") !== false) {
$i = str_replace("SO_TrackData_TrackData_", "", $valueItem->key);
$valueItem->val = json_encode($trackData[$i]);
}
}
// write srtb file
$srtbFileLocation = $this->getParameter('srtb_path').DIRECTORY_SEPARATOR.$song->getFileReference().".srtb";
file_put_contents($srtbFileLocation, json_encode( $srtbContent ));
// clean up temp files
$hf = new HelperFunctions();
$hf->delTree($extractionPath);
} catch(\Exception $e) {
throw $e;
}
} catch(\Exception $e) {
throw $e;
}
} else {
throw new \Exception("Error when extracting ZIP file");
}
}
// save in database
$em->persist($song);
$em->flush();
return $this->redirectToRoute('song.detail', ['songId' => $song->getId()]);
}
return $this->render('song/update.html.twig', $tempVars);
}
}
/**
* @Route("/song/{songId}/delete", name="song.delete")
*/
public function songDelete(Request $request, UserInterface $user, int $songId)
{
$em = $this->getDoctrine()->getManager();
$data = [];
$result = $em->getRepository(Song::class)->findOneBy(array('id' => $songId, 'uploader' => $user->getId()));
$data['song'] = $result;
if(!$result) {
throw new NotFoundHttpException();
} else {
if($request->query->get('isConfirmed')) {
// Remove .srtb File
try {
@unlink($this->getParameter('srtb_path').DIRECTORY_SEPARATOR.$result->getFileReference().".srtb");
} catch(FileNotFoundException $e) {
}
// Remove cover
try {
@unlink($this->getParameter('cover_path').DIRECTORY_SEPARATOR.$result->getFileReference().".png");
} catch(FileNotFoundException $e) {
}
// Remove .ogg files
try {
$oggFiles = glob($this->getParameter('audio_path').DIRECTORY_SEPARATOR.$result->getFileReference()."_*.ogg");
foreach($oggFiles as $oggFile) {
@unlink($oggFile);
}
} catch(FileNotFoundException $e) {
}
// remove the entity
$em->remove($result);
$em->flush();
// reditect
return $this->redirectToRoute('user.detail', ['userId' => $user->getId()]);
} else {
return $this->render('song/delete.html.twig', $data);
}
}
}
}
......@@ -58,6 +58,8 @@
This song is broken
{% elseif report.reason == "spam" %}
This song is spam
{% elseif report.reason == "metadata" %}
This song has wrong meta data
{% elseif report.reason == "other" %}
Other reson
{% endif %}
......
{% extends 'base.html.twig' %}
{% block title %}Deleting {{ song.title }}{% endblock %}
{% block content %}
<section class="section-song-delete">
<div class="box">
<div class="text">
<div class="title">Confirmation</div>
<p>Do you really want to remove your chart? <strong>This action cannot be undone!</strong></p>
</div>
<div class="song-item inactive">
<div class="song-cover" style="background-image: url({{ asset('uploads/cover/' ~ song.fileReference ~ '.png') }});">
</div>
<div class="song-metadata">
<div class="song-title">{{ song.title|default('Untitles') }}</div>
<div class="song-artist">{{ song.artist|default('Unknown') }}</div>
<div class="song-difficulties">
<img src="{{ asset('assets/img/difficultyEasy.svg') }}" class="{{ song.hasEasyDifficulty ? "active" : "" }}" alt="Easy Difficulty" />
<img src="{{ asset('assets/img/difficultyNormal.svg') }}" class="{{ song.hasNormalDifficulty ? "active" : "" }}" alt="Normal Difficulty" />
<img src="{{ asset('assets/img/difficultyHard.svg') }}" class="{{ song.hasHardDifficulty ? "active" : "" }}" alt="Hard Difficulty" />
<img src="{{ asset('assets/img/difficultyExtreme.svg') }}" class="{{ song.hasExtremeDifficulty ? "active" : "" }}" alt="Extreme Difficulty" />
<img src="{{ asset('assets/img/difficultyXD.svg') }}" class="{{ song.hasXDDifficulty ? "active" : "" }}" alt="xD Difficulty" />
</div>
</div>
</div>
<div class="actions">
<a href="{{ path('song.delete', {songId: song.id, isConfirmed: true}) }}" class="button button-primary">Yeah, I'm sure!</a>
<a href="{{ path('song.detail', {songId: song.id}) }}" class="button">No, I'm not ready!</a>
</div>
</div>
</section>
{% endblock %}
{% block styles %}
<link rel="stylesheet" href="{{ asset('assets/css/songdelete.css') }}" />
{% endblock %}
{% block scripts %}
<script>
</script>
{% endblock %}
\ No newline at end of file
......@@ -43,11 +43,17 @@
</div>
</div>
</div>
<div class="song-detail-actions">
<div class="{{ (uploader.id == app.user.id ) ? "song-detail-actions-owner" : is_granted('ROLE_MODERATOR') ? "song-detail-actions-moderator" : "song-detail-actions" }}">
<a href="{{ path('song.download', {songId: song.id}) }}" class="button button-primary">Download</a>
<button class="button-preview button">PLAY PREVIEW</button>
<a href="spinshare-song://{{ song.id }}" class="button">Open in Client</a>
<a href="{{ path('report.song', {songId: song.id}) }}" class="button">Report</a>
{% if uploader.id == app.user.id %}
<a href="{{ path('song.update', {songId: song.id}) }}" class="button button-icon"><i class="mdi mdi-pencil"></i></a>
<a href="{{ path('song.delete', {songId: song.id}) }}" class="button button-icon"><i class="mdi mdi-delete"></i></a>
{% elseif is_granted('ROLE_MODERATOR') %}
<a href="{{ path('moderation.song.remove', {songId: song.id}) }}" onclick="return confirm('Are you sure? This action cannot be undone!')" class="button button-icon"><i class="mdi mdi-delete"></i></a>
{% endif %}
</div>
</section>
{% endblock %}
......
{% extends 'base.html.twig' %}
{% block title %}Update Song{% endblock %}
{% block content %}
<section class="section-upload">
{{ form_start(uploadForm) }}
<div class="box">
<div class="title">Update Song</div>
<div class="upload-input">
{{ form_row(uploadForm.backupPath) }}
<div class="upload-input-select active">
<i class="mdi mdi-folder-upload"></i>
<div>SELECT A NEW SPIN RHYTHM XD<br />BACKUP .ZIP</div>
</div>
<div class="upload-input-selected">
<i class="mdi mdi-folder-music"></i>
<div></div>
</div>
</div>
<div class="tags-input">
{{ form_row(uploadForm.tags) }}
<div class="input-notice">Add multiple tags by seperating them with a comma. (eg: "Memes,Funny,Osu")</div>
</div>
<div class="tags-input">
{{ form_row(uploadForm.isExplicit) }}
<div class="input-notice">Are the lyrics, cover or any other meta data of this song unsuitable for children and/or not safe for work?</div>
</div>
<div class="actions">
{{ form_row(uploadForm.save) }}
</div>
</div>
{{ form_end(uploadForm) }}
</section>
{% endblock %}
{% block scripts %}
<script src="{{ asset('assets/js/upload.js') }}"></script>
{% endblock %}
{% block styles %}
<link rel="stylesheet" href="{{ asset('assets/css/upload.css') }}" />
{% endblock %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment