diff --git a/app/Resources/views/embeds/modal.html.twig b/app/Resources/views/embeds/modal.html.twig
new file mode 100644
index 0000000..619d892
--- /dev/null
+++ b/app/Resources/views/embeds/modal.html.twig
@@ -0,0 +1,19 @@
+
+
+
+ {% if block('title') is not empty %}
+
+ {% endif %}
+
+
+ {% block content %}{% endblock %}
+
+
+ {% if block('actions') is not empty %}
+
+ {% block actions %}{% endblock %}
+
+ {% endif %}
+
\ No newline at end of file
diff --git a/app/config/service/service.yml b/app/config/service/service.yml
index c0fc0f2..8b1c41a 100644
--- a/app/config/service/service.yml
+++ b/app/config/service/service.yml
@@ -17,11 +17,15 @@ services:
service.zip:
class: AppBundle\Service\ZipService
- arguments: ['@oneup_flysystem.media_filesystem', '@service.set']
+ arguments: ['@oneup_flysystem.media_filesystem', '@service.set', '@service.model']
service.set:
class: AppBundle\Service\SetService
- arguments: ['@repository.rebrickable.inventorypart'] arguments: ['@repository.rebrickable.inventorypart'] app.part_image_loader:
+ arguments: ['@repository.rebrickable.inventorypart']
+
+ service.model:
+ class: AppBundle\Service\ModelService
+
app.part_image_loader:
class: AppBundle\Imagine\PartImageLoader
arguments: ['@api.manager.rebrickable', '@oneup_flysystem.media_filesystem']
diff --git a/src/AppBundle/Controller/ModelController.php b/src/AppBundle/Controller/ModelController.php
index 3cb4cea..015693d 100644
--- a/src/AppBundle/Controller/ModelController.php
+++ b/src/AppBundle/Controller/ModelController.php
@@ -9,7 +9,9 @@ use AppBundle\Form\Filter\Model\ModelFilterType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\ResponseHeaderBag;
/**
* Part controller.
@@ -59,11 +61,11 @@ class ModelController extends Controller
*/
public function detailAction($number)
{
- $em = $this->getDoctrine()->getManager();
-
/** @var Model $model */
if ($model = $this->get('repository.ldraw.model')->findOneByNumber($number)) {
try {
+ $subparts = $this->get('service.model')->getAllSubparts($model);
+
$rbParts = $model != null ? $this->get('repository.rebrickable.part')->findAllByModel($model) : null;
$sets = $model != null ? $this->get('repository.rebrickable.set')->findAllByModel($model) : null;
@@ -74,6 +76,7 @@ class ModelController extends Controller
'rbParts' => $rbParts,
'sets' => $sets,
'related' => $related,
+ 'subparts' => $subparts,
]);
} catch (\Exception $e) {
$this->addFlash('error', $e->getMessage());
@@ -87,8 +90,21 @@ class ModelController extends Controller
* @Route("/{number}/zip", name="model_zip")
* @Method("GET")
*/
- public function zipAction(Model $model)
+ public function zipAction(Request $request, Model $model)
{
- $zip = $this->get('service.zip')->createFromModel($model);
+ $zip = $this->get('service.zip')->createFromModel($model, true);
+
+ $response = new BinaryFileResponse($zip);
+ $response->headers->set('Content-Type', 'application/zip');
+
+ // Create the disposition of the file
+ $disposition = $response->headers->makeDisposition(
+ ResponseHeaderBag::DISPOSITION_ATTACHMENT,
+ "model_{$model->getNumber()}_{$model->getName()}.zip"
+ );
+
+ $response->headers->set('Content-Disposition', $disposition);
+
+ return $response;
}
}
diff --git a/src/AppBundle/Controller/SetController.php b/src/AppBundle/Controller/SetController.php
index b581a47..98ef29c 100644
--- a/src/AppBundle/Controller/SetController.php
+++ b/src/AppBundle/Controller/SetController.php
@@ -3,14 +3,15 @@
namespace AppBundle\Controller;
use AppBundle\Api\Exception\ApiException;
-use AppBundle\Api\Exception\EmptyResponseException;
use AppBundle\Entity\Rebrickable\Inventory_Set;
use AppBundle\Entity\Rebrickable\Set;
use AppBundle\Form\Filter\Set\SetFilterType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\ResponseHeaderBag;
/**
* @Route("/sets")
@@ -55,10 +56,12 @@ class SetController extends Controller
public function detailAction(Request $request, Set $set)
{
$bricksetSet = null;
- $colors = null;
+ $partCount = $this->get('repository.rebrickable.inventoryPart')->getPartCount($set->getNumber(), false);
try {
- $bricksetSet = $this->get('api.manager.brickset')->getSetByNumber($set->getNumber());
+ if (!($bricksetSet = $this->get('api.manager.brickset')->getSetByNumber($set->getNumber()))) {
+ $this->addFlash('warning', "{$set->getNumber()} not found in Brickset database");
+ }
} catch (ApiException $e) {
$this->addFlash('error', $e->getService());
} catch (\Exception $e) {
@@ -68,6 +71,7 @@ class SetController extends Controller
return $this->render('set/detail.html.twig', [
'set' => $set,
'brset' => $bricksetSet,
+ 'partCount' => $partCount,
]);
}
@@ -113,7 +117,8 @@ class SetController extends Controller
try {
$models = $this->get('service.set')->getModels($set, false);
$spareModels = $this->get('service.set')->getModels($set, true);
- $missing = $this->get('repository.rebrickable.inventorypart')->findAllBySetNumber($set->getNumber(), false, false);
+ $missing = $this->get('service.set')->getParts($set, false, false);
+// $missing = $this->get('repository.rebrickable.inventorypart')->findAllBySetNumber($set->getNumber(), false, false);
$missingSpare = $this->get('repository.rebrickable.inventorypart')->findAllBySetNumber($set->getNumber(), true, false);
} catch (\Exception $e) {
$this->addFlash('error', $e->getMessage());
@@ -197,6 +202,22 @@ class SetController extends Controller
public function zipAction(Request $request, Set $set)
{
$sorted = $request->query->get('sorted') == 1 ? true : false;
- $this->get('service.zip')->createFromSet($set, $sorted);
+
+ $sort = $sorted ? 'sorted' : 'unsorted';
+
+ $zip = $this->get('service.zip')->createFromSet($set, $sorted);
+
+ $response = new BinaryFileResponse($zip);
+ $response->headers->set('Content-Type', 'application/zip');
+
+ // Create the disposition of the file
+ $disposition = $response->headers->makeDisposition(
+ ResponseHeaderBag::DISPOSITION_ATTACHMENT,
+ "set_{$set->getNumber()}_{$set->getName()}({$sort}).zip"
+ );
+
+ $response->headers->set('Content-Disposition', $disposition);
+
+ return $response;
}
}
diff --git a/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php b/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php
index 559fe09..d121c06 100644
--- a/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php
+++ b/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php
@@ -26,7 +26,9 @@ class Inventory_PartRepository extends BaseRepository
$queryBuilder = $this->createQueryBuilder('inventory_part')
->where('inventory_part.inventory = :inventory')
- ->setParameter('inventory', $inventory);
+ ->setParameter('inventory', $inventory)
+ ->join(Part::class, 'part', JOIN::WITH, 'inventory_part.part = part')
+ ->andWhere('part.category != 17');
if ($spare !== null) {
$queryBuilder
@@ -35,8 +37,6 @@ class Inventory_PartRepository extends BaseRepository
}
if ($model !== null) {
- $queryBuilder
- ->join(Part::class, 'part', JOIN::WITH, 'inventory_part.part = part');
if ($model === true) {
$queryBuilder->andWhere('part.model IS NOT NULL');
} else {
@@ -59,4 +59,32 @@ class Inventory_PartRepository extends BaseRepository
return $queryBuilder->getQuery()->getResult();
}
+
+ public function getPartCount($number, $spare = null, $model = null)
+ {
+ $inventory = $this->getEntityManager()->getRepository(Inventory::class)->findNewestInventoryBySetNumber($number);
+
+ $queryBuilder = $this->createQueryBuilder('inventory_part')
+ ->where('inventory_part.inventory = :inventory')
+ ->setParameter('inventory', $inventory)
+ ->join(Part::class, 'part', JOIN::WITH, 'inventory_part.part = part')
+ ->andWhere('part.category != 17')
+ ->select('SUM(inventory_part.quantity) as parts');
+
+ if ($spare !== null) {
+ $queryBuilder
+ ->andWhere('inventory_part.spare = :spare')
+ ->setParameter('spare', $spare);
+ }
+
+ if ($model !== null) {
+ if ($model === true) {
+ $queryBuilder->andWhere('part.model IS NOT NULL');
+ } else {
+ $queryBuilder->andWhere('part.model IS NULL');
+ }
+ }
+
+ return $queryBuilder->getQuery()->getSingleScalarResult();
+ }
}
diff --git a/src/AppBundle/Repository/Rebrickable/ThemeRepository.php b/src/AppBundle/Repository/Rebrickable/ThemeRepository.php
index fcdaccd..cddfdf1 100644
--- a/src/AppBundle/Repository/Rebrickable/ThemeRepository.php
+++ b/src/AppBundle/Repository/Rebrickable/ThemeRepository.php
@@ -19,4 +19,12 @@ class ThemeRepository extends BaseRepository
return $queryBuilder->getQuery()->getResult();
}
+
+ public function findAllMain()
+ {
+ $queryBuilder = $this->createQueryBuilder('theme')
+ ->where('theme.parent IS NULL');
+
+ return $queryBuilder->getQuery()->getResult();
+ }
}
diff --git a/src/AppBundle/Service/ModelService.php b/src/AppBundle/Service/ModelService.php
new file mode 100644
index 0000000..ecd6a75
--- /dev/null
+++ b/src/AppBundle/Service/ModelService.php
@@ -0,0 +1,35 @@
+getSubparts() as $subpart) {
+ $this->resursiveLoadModels($subpart->getSubpart(), $subpart->getCount());
+ }
+
+ return $this->models;
+ }
+
+ private function resursiveLoadModels(Model $model, $quantity = 1)
+ {
+ if (($model->getSubparts()->count() !== 0)) {
+ foreach ($model->getSubparts() as $subpart) {
+ $this->resursiveLoadModels($subpart->getSubpart(), $subpart->getCount());
+ }
+ } else {
+ $q = isset($this->models[$model->getNumber()]['quantity']) ? $this->models[$model->getNumber()]['quantity'] : 0;
+
+ $this->models[$model->getNumber()] = [
+ 'quantity' => $q + $quantity,
+ 'model' => $model,
+ ];
+ }
+ }
+}
diff --git a/src/AppBundle/Service/SetService.php b/src/AppBundle/Service/SetService.php
index 285e763..6d95efb 100644
--- a/src/AppBundle/Service/SetService.php
+++ b/src/AppBundle/Service/SetService.php
@@ -3,13 +3,13 @@
namespace AppBundle\Service;
use AppBundle\Entity\LDraw\Model;
- use AppBundle\Entity\Rebrickable\Inventory_Part;
- use AppBundle\Entity\Rebrickable\Set;
- use AppBundle\Repository\Rebrickable\Inventory_PartRepository;
+use AppBundle\Entity\Rebrickable\Inventory_Part;
+use AppBundle\Entity\Rebrickable\Set;
+use AppBundle\Repository\Rebrickable\Inventory_PartRepository;
- class SetService
- {
- /** @var Inventory_PartRepository */
+class SetService
+{
+ /** @var Inventory_PartRepository */
private $inventoryPartRepository;
/**
@@ -110,7 +110,6 @@ use AppBundle\Entity\LDraw\Model;
return $models;
}
-
/**
* Get array models grouped by color.
* [
@@ -163,4 +162,31 @@ use AppBundle\Entity\LDraw\Model;
return $colors;
}
- }
+
+ /*
+ * @param Set $set
+ * @param bool $spare If true - add only spare parts, false - add only regular parts, null - add all parts
+ *
+ * @return array
+ */
+ public function getParts(Set $set, $spare = null, $model = false)
+ {
+ $parts = [];
+
+ $inventoryParts = $this->inventoryPartRepository->findAllBySetNumber($set->getNumber(), $spare, $model);
+
+ /** @var Inventory_Part $inventoryPart */
+ foreach ($inventoryParts as $inventoryPart) {
+ if (isset($parts[$inventoryPart->getPart()->getNumber()])) {
+ $parts[$inventoryPart->getPart()->getNumber()]['quantity'] += $inventoryPart->getQuantity();
+ } else {
+ $parts[$inventoryPart->getPart()->getNumber()] = [
+ 'part' => $inventoryPart->getPart(),
+ 'quantity' => $inventoryPart->getQuantity(),
+ ];
+ }
+ }
+
+ return $parts;
+ }
+}
diff --git a/src/AppBundle/Service/ZipService.php b/src/AppBundle/Service/ZipService.php
index bc427fe..6a8fc32 100644
--- a/src/AppBundle/Service/ZipService.php
+++ b/src/AppBundle/Service/ZipService.php
@@ -5,11 +5,10 @@ namespace AppBundle\Service;
use AppBundle\Entity\LDraw\Model;
use AppBundle\Entity\Rebrickable\Set;
use League\Flysystem\Filesystem;
-use ZipStream\ZipStream;
class ZipService
{
- /** @var ZipStream */
+ /** @var \ZipArchive */
private $archive;
/** @var Filesystem */
@@ -18,26 +17,41 @@ class ZipService
/** @var SetService */
private $setService;
+ /** @var ModelService */
+ private $modelService;
+
+ private $zipName;
+
+ private $models;
+
/**
* ZipService constructor.
*
* @param $mediaFilesystem
* @param $setService
*/
- public function __construct($mediaFilesystem, $setService)
+ public function __construct($mediaFilesystem, $setService, $modelService)
{
$this->mediaFilesystem = $mediaFilesystem;
$this->setService = $setService;
+ $this->modelService = $modelService;
+ }
+
+ private function createZip($path)
+ {
+ $archive = new \ZipArchive();
+ $archive->open($path, \ZipArchive::CREATE);
+
+ return $archive;
}
public function createFromSet(Set $set, $sorted = false)
{
$sort = $sorted ? 'sorted' : 'unsorted';
+ $this->zipName = "set_{$set->getNumber()}_{$set->getName()}({$sort})";
- $filename = "set_{$set->getNumber()}_{$set->getName()}({$sort}).zip";
-
- // Initialize zip stream
- $this->archive = new ZipStream($filename);
+ $zipPath = tempnam(sys_get_temp_dir(), 'printabrick');
+ $this->archive = $this->createZip($zipPath);
if ($sorted) {
$this->addSetGroupedByColor($set);
@@ -45,23 +59,35 @@ class ZipService
$this->addSet($set);
}
- $this->archive->finish();
+ $this->addLicense();
+ $this->archive->close();
- return $this->archive;
+ return $zipPath;
}
public function createFromModel(Model $model, $subparts = false)
{
- $filename = "model_{$model->getNumber()}.zip";
+ $this->zipName = "model_{$model->getNumber()}";
- // Initialize zip stream
- $this->archive = new ZipStream($filename);
+ $zipPath = tempnam(sys_get_temp_dir(), 'printabrick');
+ $this->archive = $this->createZip($zipPath);
- $this->addModel($model);
+ $filename = "{$this->zipName}/{$model->getNumber()}.stl";
+ $this->addModel($filename, $model);
- $this->archive->finish();
+ if ($subparts) {
+ foreach ($this->modelService->getAllSubparts($model) as $subpart) {
+ $submodel = $subpart['model'];
+ $filename = "{$this->zipName}/submodels/{$submodel->getNumber()}_({$subpart['quantity']}x).stl";
- return $this->archive;
+ $this->addModel($filename, $submodel);
+ }
+ }
+
+ $this->addLicense();
+ $this->archive->close();
+
+ return $zipPath;
}
/**
@@ -81,9 +107,9 @@ class ZipService
$model = $modelArray['model'];
$quantity = $modelArray['quantity'];
- $filename = "{$color->getName()}/{$model->getNumber()}_({$quantity}x).stl";
+ $filename = "{$this->zipName}/{$color->getName()}/{$model->getNumber()}_({$quantity}x).stl";
- $this->archive->addFile($filename, $this->mediaFilesystem->read($model->getPath()));
+ $this->addModel($filename, $model);
}
}
}
@@ -99,26 +125,33 @@ class ZipService
$models = $this->setService->getModels($set, $spare);
foreach ($models as $number => $array) {
+ $model = $array['model'];
$quantity = $array['quantity'];
- $filename = $number."_({$quantity}x).stl";
+ $filename = "{$this->zipName}/{$number}_({$quantity}x).stl";
+ $this->models[$number] = $array['model'];
- $this->archive->addFile($filename, $this->mediaFilesystem->read($array['model']->getPath()));
+ $this->addModel($filename, $model);
}
}
- public function addModel(Model $model, $count = 1, $folder = '')
+ private function addModel($path, $model)
{
- $filename = $folder.$model->getNumber()."_({$count}x).stl";
-
- $this->archive->addFile($filename, $this->mediaFilesystem->read($model->getPath()));
-
- foreach ($model->getSubparts() as $subpart) {
- $this->addModel($subpart->getSubpart(), $subpart->getCount(), $folder.$model->getNumber().'_subparts/');
- }
+ $this->archive->addFromString($path, $this->mediaFilesystem->read($model->getPath()));
+ $this->models[$model->getNumber()] = $model;
}
- // TODO add licence file and information to zip file
- public function addLicence()
+ private function addLicense()
{
+ $text = sprintf('All stl files in this archive were converted by LDView from LDraw Library http://www.ldraw.org/'."\n\n");
+ $text .= sprintf('Files are licensed under the Creative Commons - Attribution license.'."\n");
+ $text .= sprintf('http://creativecommons.org/licenses/by/2.0/'."\n\n");
+
+ $text .= sprintf('Attribution:'."\n"."\n");
+
+ foreach ($this->models as $model) {
+ $text .= sprintf('%s - "%s" by %s'."\n", $model->getNumber(), $model->getName(), $model->getAuthor()->getName());
+ }
+
+ $this->archive->addFromString("{$this->zipName}/LICENSE.txt", $text);
}
}