Skip to content
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
11 changes: 11 additions & 0 deletions assets/js/ui.decks.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@
}, 500);
};

ui.download_selected = function(ids)
{
var $form = $('#download-deck-list-form');
var $input = $('#download-deck-list-id');
$input.val(ids.join('-'));
$form.submit();
};

ui.tag_remove_process = function tag_remove_process(event)
{
event.preventDefault();
Expand Down Expand Up @@ -187,6 +195,9 @@
case 'btn-delete-selected':
ui.confirm_delete_all(ids);
break;
case 'btn-download-selected':
ui.download_selected(ids);
$(".selected-decks-dropdown-toggle").dropdown("toggle");
}
return false;
};
Expand Down
144 changes: 112 additions & 32 deletions src/Controller/BuilderController.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,21 +201,24 @@ function (FactionInterface $faction) {
* @param Request $request
* @param DeckImportService $deckImportService
* @param DeckManager $deckManager
* @return RedirectResponse|Response
* @throws ORMException
* @throws OptimisticLockException
* @param SessionInterface $session
* @param TranslatorInterface $translator
* @throws BadRequestHttpException
* @return RedirectResponse
*/
public function fileimportAction(Request $request, DeckImportService $deckImportService, DeckManager $deckManager)
{
public function fileimportAction(
Request $request,
DeckImportService $deckImportService,
DeckManager $deckManager,
SessionInterface $session,
TranslatorInterface $translator
) {
$uploadedFile = $request->files->get('upfile');
if (!isset($uploadedFile)) {
throw new BadRequestHttpException("No file");
}

$origname = $uploadedFile->getClientOriginalName();
$origext = $uploadedFile->getClientOriginalExtension();
$filename = $uploadedFile->getPathname();
$name = str_replace(".$origext", '', $origname);

if (function_exists("finfo_open")) {
// return mime type ala mimetype extension
Expand All @@ -229,33 +232,72 @@ public function fileimportAction(Request $request, DeckImportService $deckImport
}
}

$data = $deckImportService->parseTextImport(file_get_contents($filename));
$parsedData = $deckImportService->parseTextImport(file_get_contents($filename));

if (empty($data['faction'])) {
return $this->render(
'Default/error.html.twig',
[
'error' => "Unable to recognize the Faction of the deck.",
]
// Cancel import if number of given lists exceeds the number of available deck slots.
// No partial import of (bulk) uploads is supported.
/** @var UserInterface $user */
$user = $this->getUser();
$existingDecks = $deckManager->getByUser($user);
$numberSuccessfullyParsedDecks = count($parsedData['decks']);
$numberOfFailedParsedDecks = count($parsedData['errors']);
$errorMessages = array_unique($parsedData['errors']);
$numberOfDecksUploaded = $numberSuccessfullyParsedDecks + $numberOfFailedParsedDecks;

if ($user->getMaxNbDecks() < $numberOfDecksUploaded + count($existingDecks)) {
$session->getFlashBag()->set(
'error',
$translator->trans('decks.import.error.general')
. ' ' .
$translator->trans('decks.save.outOfSlots')
);
return $this->redirect($this->generateUrl('decks_list'));
}

$deck = new Deck();
$deck->setUuid(Uuid::uuid4());
// finally, import all the "good" decks(s)
foreach ($parsedData['decks'] as $data) {
$deck = new Deck();
$deck->setUuid(Uuid::uuid4());

$deckManager->save(
$this->getUser(),
$deck,
null,
$name,
$data['faction'],
$data['description'],
null,
$data['content'],
null
);
$deckManager->save(
$this->getUser(),
$deck,
null,
$data['name'],
$data['faction'],
$data['description'],
null,
$data['content'],
null
);
}

$this->getDoctrine()->getManager()->flush();
if ($numberSuccessfullyParsedDecks) {
$session->getFlashBag()->set(
'notice',
$translator->transChoice(
"decks.import.success",
$numberOfDecksUploaded,
[ '%success%' => $numberSuccessfullyParsedDecks, '%all%' => $numberOfDecksUploaded ]
)
);
}
if ($numberOfFailedParsedDecks) {
$session->getFlashBag()->set(
'error',
$translator->transChoice(
"decks.import.failures",
$numberOfDecksUploaded,
[ '%failures%' => $numberOfFailedParsedDecks, '%all%' => $numberOfDecksUploaded ]
) . " " .
$translator->transChoice(
"decks.import.failureReasons",
count($errorMessages),
[ '%reasons%' => implode('", "', $errorMessages) ]
)
);
}

return $this->redirect($this->generateUrl('decks_list'));
}
Expand Down Expand Up @@ -350,11 +392,10 @@ public function cloneAction($deck_uuid)
* @Route("/deck/save", name="deck_save", methods={"POST"})
* @param Request $request
* @param DeckManager $deckManager
* @param TranslatorInterface $translator
* @return RedirectResponse|Response
* @throws ORMException
* @throws OptimisticLockException
*/
public function saveAction(Request $request, DeckManager $deckManager)
public function saveAction(Request $request, DeckManager $deckManager, TranslatorInterface $translator)
{

/* @var EntityManager $em*/
Expand All @@ -363,7 +404,7 @@ public function saveAction(Request $request, DeckManager $deckManager)
$user = $this->getUser();
if (count($user->getDecks()) > $user->getMaxNbDecks()) {
return new Response(
'You have reached the maximum number of decks allowed. Delete some decks or increase your reputation.'
$translator->trans('decks.save.outOfSlots')
);
}

Expand Down Expand Up @@ -463,6 +504,45 @@ public function deleteAction(Request $request, SessionInterface $session)
return $this->redirect($this->generateUrl('decks_list'));
}

/**
* @Route("/deck/download_list", name="deck_download_list", methods={"POST"})
* @param Request $request
* @param SessionInterface $session
* @return Response
*/
public function downloadListAction(Request $request, SessionInterface $session)
{
/* @var $em EntityManager */
$em = $this->getDoctrine()->getManager();
$ids = explode('-', $request->get('ids'));
$decks = $em->getRepository(Deck::class)->findBy(['id' => $ids]);

$currentUserId = $this->getUser()->getId();
$decks = array_values(array_filter($decks, function (DeckInterface $deck) use ($currentUserId) {
return $currentUserId === $deck->getUser()->getId();
}));

$exports = [];
foreach ($decks as $deck) {
$content = $this->renderView('Export/default.txt.twig', [ "deck" => $deck->getTextExport() ]);
$exports[] = str_replace("\n", "\r\n", $content);
}

$response = new Response();
$response->headers->set('Content-Type', 'text/plain');
$response->headers->set(
'Content-Disposition',
$response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
'decks.txt'
)
);

$response->setContent(implode("\r\n===\r\n", $exports));

return $response;
}

/**
* @Route("/deck/delete_list", name="deck_delete_list", methods={"POST"})
* @param Request $request
Expand Down
118 changes: 102 additions & 16 deletions src/Services/DeckImportService.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
use App\Entity\Faction;
use App\Entity\Pack;
use App\Entity\PackInterface;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
* Description of DeckImportService
Expand All @@ -16,29 +17,80 @@
*/
class DeckImportService
{
public EntityManagerInterface $em;
// three equal signs (or more) in a row are used
// to separate multiple decks in one upload
const DECKS_DELIMITER_REGEXP = '/[=]{3,}/';

public function __construct(EntityManagerInterface $em)
// <quantity>x<card name> (<pack code|name>)
// <quantity><card name> (<pack code|name>)
const CARD_WITH_PACK_INFO_REGEXP = '/^\s*(\d)x?([^(]+) \(([^)]+)/u';

// <quantity>x<card name>
// <quantity><card name>
const CARD_WITHOUT_PACK_INFO_REGEXP = '/^\s*(\d)x?([\pLl\pLu\pN\-.\'!:" ]+)/u';

// #<three digits> <quantity>x<card name>
// #<three digits> <quantity><card name>
const CARD_WITHOUT_PACK_INFO_ALT1_REGEXP = '/^\s*#\d{3}\s(\d)x?([\pLl\pLu\pN\-.\'!: ]+)/u';

// <card name>x<quantity>
const CARD_WITHOUT_PACK_INFO_ALT2_REGEXP = '/^([^(]+).*x(\d)/';

// <card name>
const SINGLE_CARD_WITHOUT_PACK_INFO_REGEXP = '/^([^(]+)/';

protected EntityManagerInterface $em;

protected TranslatorInterface $translator;

public function __construct(EntityManagerInterface $em, TranslatorInterface $translator)
{
$this->em = $em;
$this->translator = $translator;
}

/**
* @param string $text
* @return array
*/
public function parseTextImport($text)
public function parseTextImport(string $text): array
{
$data = [
'content' => [],
'faction' => null,
'description' => ''
$rhett = [
'decks' => [],
'errors' => [],
];

$lines = explode("\n", $text);
$text = trim($text);
if ('' === $text) {
return $rhett;
}

$textChunks = preg_split(self::DECKS_DELIMITER_REGEXP, $text, null, PREG_SPLIT_NO_EMPTY);

$decks = [];

// trim whitespace off of all lines and filter out any blank lines
$removeFiller = function (array $lines) {
$lines = array_map(function ($line) {
return trim($line);
}, $lines);
$lines = array_filter($lines, function ($line) {
return '' !== $line;
});
return array_values($lines);
};

foreach ($textChunks as $text) {
$lines = explode("\n", trim($text));
$lines = $removeFiller($lines);

if (!empty($lines)) {
$decks[] = $lines;
}
}

if (empty($lines)) {
return $data;
if (empty($decks)) {
return $rhett;
}

// load all packs upfront and map them by their names and codes for easy lookup below
Expand All @@ -50,25 +102,55 @@ public function parseTextImport($text)
return $pack->getCode();
}, $packs), $packs);

foreach ($decks as $lines) {
try {
$rhett['decks'][] = $this->parseOneTextImport($lines, $packsByName, $packsByCode);
} catch (Exception $e) {
$rhett['errors'][] = $e->getMessage();
}
}

return $rhett;
}

/**
* @param array $lines
* @param array $packsByName
* @param array $packsByCode
* @return array
* @throws Exception
*/
protected function parseOneTextImport(array $lines, array $packsByName, array $packsByCode): array
{
$data = [
'content' => [],
'faction' => null,
'description' => '',
'name' => 'new deck',
];

// set the deck's name from the first line in the given import
$data['name'] = $lines[0];

foreach ($lines as $line) {
$matches = [];
$packNameOrCode = null;
$card = null;

if (preg_match('/^\s*(\d)x?([^(]+) \(([^)]+)/u', $line, $matches)) {
if (preg_match(self::CARD_WITH_PACK_INFO_REGEXP, $line, $matches)) {
$quantity = intval($matches[1]);
$name = trim($matches[2]);
$packNameOrCode = trim($matches[3]);
} elseif (preg_match('/^\s*(\d)x?([\pLl\pLu\pN\-\.\'\!\:" ]+)/u', $line, $matches)) {
} elseif (preg_match(self::CARD_WITHOUT_PACK_INFO_REGEXP, $line, $matches)) {
$quantity = intval($matches[1]);
$name = trim($matches[2]);
} elseif (preg_match('/^\s*#\d{3}\s(\d)x?([\pLl\pLu\pN\-\.\'\!\: ]+)/u', $line, $matches)) {
} elseif (preg_match(self::CARD_WITHOUT_PACK_INFO_ALT1_REGEXP, $line, $matches)) {
$quantity = intval($matches[1]);
$name = trim($matches[2]);
} elseif (preg_match('/^([^\(]+).*x(\d)/', $line, $matches)) {
} elseif (preg_match(self::CARD_WITHOUT_PACK_INFO_ALT2_REGEXP, $line, $matches)) {
$quantity = intval($matches[2]);
$name = trim($matches[1]);
} elseif (preg_match('/^([^\(]+)/', $line, $matches)) {
} elseif (preg_match(self::SINGLE_CARD_WITHOUT_PACK_INFO_REGEXP, $line, $matches)) {
$quantity = 1;
$name = trim($matches[1]);
} else {
Expand Down Expand Up @@ -107,6 +189,10 @@ public function parseTextImport($text)
}
}

if (empty($data['faction'])) {
throw new Exception($this->translator->trans('decks.import.error.cannotFindFaction'));
}

return $data;
}
}
Loading