diff --git a/src/Controller/AttachmentFileController.php b/src/Controller/AttachmentFileController.php
index bdaf1ebc..d376b148 100644
--- a/src/Controller/AttachmentFileController.php
+++ b/src/Controller/AttachmentFileController.php
@@ -32,10 +32,15 @@
namespace App\Controller;
+use App\DataTables\AttachmentDataTable;
+use App\DataTables\PartsDataTable;
use App\Entity\Attachments\Attachment;
+use App\Entity\Attachments\PartAttachment;
use App\Services\AttachmentHelper;
+use Omines\DataTablesBundle\DataTableFactory;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Annotation\Route;
@@ -104,4 +109,26 @@ class AttachmentFileController extends AbstractController
return $response;
}
+ /**
+ * @Route("/attachment/list", name="attachment_list")
+ * @param DataTableFactory $dataTable
+ * @param Request $request
+ * @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\Response
+ */
+ public function attachmentsTable(DataTableFactory $dataTable, Request $request)
+ {
+ $this->denyAccessUnlessGranted('read', new PartAttachment());
+
+ $table = $dataTable->createFromType(AttachmentDataTable::class)
+ ->handleRequest($request);
+
+ if ($table->isCallback()) {
+ return $table->getResponse();
+ }
+
+ return $this->render('attachment_list.html.twig', [
+ 'datatable' => $table
+ ]);
+ }
+
}
\ No newline at end of file
diff --git a/src/DataTables/AttachmentDataTable.php b/src/DataTables/AttachmentDataTable.php
new file mode 100644
index 00000000..d7424fee
--- /dev/null
+++ b/src/DataTables/AttachmentDataTable.php
@@ -0,0 +1,222 @@
+translator = $translator;
+ $this->entityURLGenerator = $entityURLGenerator;
+ $this->attachmentHelper = $attachmentHelper;
+ $this->elementTypeNameGenerator = $elementTypeNameGenerator;
+ }
+
+ protected function getQuery(QueryBuilder $builder)
+ {
+ $builder->distinct()->select('attachment')
+ ->addSelect('attachment_type')
+ //->addSelect('element')
+ ->from(Attachment::class, 'attachment')
+ ->leftJoin('attachment.attachment_type', 'attachment_type');
+ //->leftJoin('attachment.element', 'element');
+ }
+
+ /**
+ * @param DataTable $dataTable
+ * @param array $options
+ */
+ public function configure(DataTable $dataTable, array $options)
+ {
+ $dataTable->add('picture', TextColumn::class, [
+ 'label' => '',
+ 'render' => function ($value, Attachment $context) {
+ if ($context->isPicture()
+ && !$context->isExternal()
+ && $this->attachmentHelper->isFileExisting($context)) {
+ return sprintf(
+ '',
+ 'Part image',
+ $this->entityURLGenerator->viewURL($context),
+ 'img-fluid hoverpic'
+ );
+ }
+
+ return '';
+ }
+ ]);
+
+ $dataTable->add('name', TextColumn::class, [
+ 'label' => $this->translator->trans('attachment.edit.name'),
+ 'render' => function ($value, Attachment $context) {
+ //Link to external source
+ if ($context->isExternal()) {
+ return sprintf(
+ '%s',
+ htmlspecialchars($context->getURL()),
+ htmlspecialchars($value)
+ );
+ }
+
+ if ($this->attachmentHelper->isFileExisting($context)) {
+ return sprintf(
+ '%s',
+ $this->entityURLGenerator->viewURL($context),
+ htmlspecialchars($value)
+ );
+ }
+
+ return $value;
+ }
+ ]);
+
+ $dataTable->add('attachment_type', TextColumn::class, [
+ 'field' => 'attachment_type.name',
+ 'render' => function ($value, Attachment $context) {
+ return sprintf(
+ '%s',
+ $this->entityURLGenerator->editURL($context->getAttachmentType()),
+ htmlspecialchars($value)
+ );
+ }
+ ]);
+
+ $dataTable->add('element', TextColumn::class, [
+ 'label' => $this->translator->trans('attachment.table.element'),
+ //'propertyPath' => 'element.name',
+ 'render' => function ($value, Attachment $context) {
+ return sprintf(
+ '%s',
+ $this->entityURLGenerator->infoURL($context->getElement()),
+ $this->elementTypeNameGenerator->getTypeNameCombination($context->getElement(), true)
+ );
+ }
+ ]);
+
+ $dataTable->add('filename', TextColumn::class, [
+ 'propertyPath' => 'filename'
+ ]);
+
+ $dataTable->add('filesize', TextColumn::class, [
+ 'render' => function ($value, Attachment $context) {
+ if ($this->attachmentHelper->isFileExisting($context)) {
+ return $this->attachmentHelper->getHumanFileSize($context);
+ }
+ if ($context->isExternal()) {
+ return '' . $this->translator->trans('attachment.external') . '';
+ }
+
+ return sprintf(
+ '
+ %s
+ ',
+ $this->translator->trans('attachment.file_not_found')
+ );
+ }
+ ]);
+
+ $dataTable
+ ->add('addedDate', LocaleDateTimeColumn::class, [
+ 'label' => $this->translator->trans('part.table.addedDate'),
+ 'visible' => false
+ ])
+ ->add('lastModified', LocaleDateTimeColumn::class, [
+ 'label' => $this->translator->trans('part.table.lastModified'),
+ 'visible' => false
+ ]);
+
+ $dataTable->add('show_in_table', BoolColumn::class, [
+ 'label' => $this->translator->trans('attachment.edit.show_in_table'),
+ 'trueValue' => $this->translator->trans('true'),
+ 'falseValue' => $this->translator->trans('false'),
+ 'nullValue' => '',
+ 'visible' => false
+ ]);
+
+ $dataTable->add('isPicture', BoolColumn::class, [
+ 'label' => $this->translator->trans('attachment.edit.isPicture'),
+ 'trueValue' => $this->translator->trans('true'),
+ 'falseValue' => $this->translator->trans('false'),
+ 'nullValue' => '',
+ 'visible' => false,
+ 'propertyPath' => 'picture'
+ ]);
+
+ $dataTable->add('is3DModel', BoolColumn::class, [
+ 'label' => $this->translator->trans('attachment.edit.is3DModel'),
+ 'trueValue' => $this->translator->trans('true'),
+ 'falseValue' => $this->translator->trans('false'),
+ 'nullValue' => '',
+ 'visible' => false,
+ 'propertyPath' => '3dmodel'
+ ]);
+
+ $dataTable->add('isBuiltin', BoolColumn::class, [
+ 'label' => $this->translator->trans('attachment.edit.isBuiltin'),
+ 'trueValue' => $this->translator->trans('true'),
+ 'falseValue' => $this->translator->trans('false'),
+ 'nullValue' => '',
+ 'visible' => false,
+ 'propertyPath' => 'builtin'
+ ]);
+
+ $dataTable->createAdapter(ORMAdapter::class, [
+ 'entity' => Attachment::class,
+ 'query' => function (QueryBuilder $builder) {
+ $this->getQuery($builder);
+ },
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/src/Exceptions/EntityNotSupported.php b/src/Exceptions/EntityNotSupportedException.php
similarity index 94%
rename from src/Exceptions/EntityNotSupported.php
rename to src/Exceptions/EntityNotSupportedException.php
index 91963a8d..8253bc65 100644
--- a/src/Exceptions/EntityNotSupported.php
+++ b/src/Exceptions/EntityNotSupportedException.php
@@ -29,6 +29,6 @@
namespace App\Exceptions;
-class EntityNotSupported extends \Exception
+class EntityNotSupportedException extends \InvalidArgumentException
{
}
diff --git a/src/Services/ElementTypeNameGenerator.php b/src/Services/ElementTypeNameGenerator.php
new file mode 100644
index 00000000..f6bf8094
--- /dev/null
+++ b/src/Services/ElementTypeNameGenerator.php
@@ -0,0 +1,132 @@
+translator = $translator;
+
+ //Child classes has to become before parent classes
+ $this->mapping = [
+ Attachment::class => $this->translator->trans('attachment.label'),
+ Category::class => $this->translator->trans('category.label'),
+ AttachmentType::class => $this->translator->trans('attachment_type.label'),
+ Device::class => $this->translator->trans('device.label'),
+ Footprint::class => $this->translator->trans('footprint.label'),
+ Manufacturer::class => $this->translator->trans('manufacturer.label'),
+ MeasurementUnit::class => $this->translator->trans('measurement_unit.label'),
+ Part::class => $this->translator->trans('part.label'),
+ PartLot::class => $this->translator->trans('part_lot.label'),
+ Storelocation::class => $this->translator->trans('storelocation.label'),
+ Supplier::class => $this->translator->trans('supplier.label'),
+ Currency::class => $this->translator->trans('currency.label'),
+ Orderdetail::class => $this->translator->trans('orderdetail.label'),
+ Pricedetail::class => $this->translator->trans('pricedetail.label'),
+ Group::class => $this->translator->trans('group.label'),
+ User::class => $this->translator->trans('user.label'),
+ ];
+ }
+
+ /**
+ * Gets an localized label for the type of the entity.
+ * A part element becomes "Part" ("Bauteil" in german) and a category object becomes "Category".
+ * Useful when the type should be shown to user.
+ * Throws an exception if the class is not supported.
+ * @param DBElement $entity The element for which the label should be generated
+ * @return string The locatlized label for the entity type.
+ * @throws EntityNotSupportedException When the passed entity is not supported.
+ */
+ public function getLocalizedTypeLabel(DBElement $entity) : string
+ {
+ //Check if we have an direct array entry for our entity class, then we can use it
+ if (isset($this->mapping[get_class($entity)])) {
+ return $this->mapping[get_class($entity)];
+ }
+
+ //Otherwise iterate over array and check for inheritance (needed when the proxy element from doctrine are passed)
+ foreach ($this->mapping as $class => $translation) {
+ if ($entity instanceof $class) {
+ return $translation;
+ }
+ }
+
+ //When nothing was found throw an exception
+ throw new EntityNotSupportedException(
+ sprintf('No localized label for the element with type %s was found!', get_class($entity))
+ );
+ }
+
+ /**
+ * Returns a string like in the format ElementType: ElementName.
+ * For example this could be something like: "Part: BC547".
+ * It uses getLocalizedLabel to determine the type.
+ * @param NamedDBElement $entity The entity for which the string should be generated.
+ * @param bool $use_html If set to true, a html string is returned, where the type is set italic
+ * @return string The localized string
+ */
+ public function getTypeNameCombination(NamedDBElement $entity, bool $use_html = false) : string
+ {
+ $type = $this->getLocalizedTypeLabel($entity);
+ if ($use_html) {
+ return '' . $type . ': ' . $entity->getName();
+ }
+ return $type . ": " . htmlspecialchars($entity->getName());
+ }
+}
\ No newline at end of file
diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php
index ef568b87..c032cc2b 100644
--- a/src/Services/EntityURLGenerator.php
+++ b/src/Services/EntityURLGenerator.php
@@ -44,7 +44,7 @@ use App\Entity\Parts\Supplier;
use App\Entity\PriceInformations\Currency;
use App\Entity\UserSystem\Group;
use App\Entity\UserSystem\User;
-use App\Exceptions\EntityNotSupported;
+use App\Exceptions\EntityNotSupportedException;
use Symfony\Component\HttpKernel\HttpCache\Store;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@@ -72,7 +72,7 @@ class EntityURLGenerator
* @param array $map The map that should be used for determing the controller
* @param $entity mixed The entity for which the controller name should be determined.
* @return string The name of the controller fitting the entity class
- * @throws EntityNotSupported
+ * @throws EntityNotSupportedException
*/
protected function mapToController(array $map, $entity): string
{
@@ -87,7 +87,7 @@ class EntityURLGenerator
}
}
- throw new EntityNotSupported(sprintf(
+ throw new EntityNotSupportedException(sprintf(
'The given entity is not supported yet! Passed class type: %s',
get_class($entity)
));
@@ -104,7 +104,7 @@ class EntityURLGenerator
* @param $entity mixed The element for which the page should be generated.
* @param string $type The page type. Currently supported: 'info', 'edit', 'create', 'clone', 'list'/'list_parts'
* @return string The link to the desired page.
- * @throws EntityNotSupported Thrown if the entity is not supported for the given type.
+ * @throws EntityNotSupportedException Thrown if the entity is not supported for the given type.
* @throws \InvalidArgumentException Thrown if the givent type is not existing.
*/
public function getURL($entity, string $type)
@@ -142,7 +142,7 @@ class EntityURLGenerator
}
//Otherwise throw an error
- throw new EntityNotSupported('The given entity is not supported yet!');
+ throw new EntityNotSupportedException('The given entity is not supported yet!');
}
public function downloadURL($entity): string
@@ -155,7 +155,7 @@ class EntityURLGenerator
}
//Otherwise throw an error
- throw new EntityNotSupported(sprintf(
+ throw new EntityNotSupportedException(sprintf(
'The given entity is not supported yet! Passed class type: %s',
get_class($entity)
));
@@ -166,12 +166,25 @@ class EntityURLGenerator
*
* @param $entity mixed The entity for which the info should be generated.
* @return string The URL to the info page
- * @throws EntityNotSupported If the method is not supported for the given Entity
+ * @throws EntityNotSupportedException If the method is not supported for the given Entity
*/
public function infoURL(DBElement $entity): string
{
$map = [
- Part::class => 'part_info'
+ Part::class => 'part_info',
+
+ //As long we does not have own things for it use edit page
+ AttachmentType::class => 'attachment_type_edit',
+ Category::class => 'category_edit',
+ Device::class => 'device_edit',
+ Supplier::class => 'supplier_edit',
+ Manufacturer::class => 'manufacturer_edit',
+ Storelocation::class => 'store_location_edit',
+ Footprint::class => 'footprint_edit',
+ User::class => 'user_edit',
+ Currency::class => 'currency_edit',
+ MeasurementUnit::class => 'measurement_unit_edit',
+ Group::class => 'group_edit'
];
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
@@ -182,7 +195,7 @@ class EntityURLGenerator
*
* @param $entity mixed The entity for which the edit link should be generated.
* @return string The URL to the edit page.
- * @throws EntityNotSupported If the method is not supported for the given Entity
+ * @throws EntityNotSupportedException If the method is not supported for the given Entity
*/
public function editURL($entity): string
{
@@ -209,7 +222,7 @@ class EntityURLGenerator
*
* @param $entity mixed The entity for which the link should be generated.
* @return string The URL to the page.
- * @throws EntityNotSupported If the method is not supported for the given Entity
+ * @throws EntityNotSupportedException If the method is not supported for the given Entity
*/
public function createURL($entity): string
{
@@ -237,7 +250,7 @@ class EntityURLGenerator
*
* @param $entity mixed The entity for which the link should be generated.
* @return string The URL to the page.
- * @throws EntityNotSupported If the method is not supported for the given Entity
+ * @throws EntityNotSupportedException If the method is not supported for the given Entity
*/
public function cloneURL(DBElement $entity): string
{
@@ -253,7 +266,7 @@ class EntityURLGenerator
*
* @param $entity mixed The entity for which the link should be generated.
* @return string The URL to the page.
- * @throws EntityNotSupported If the method is not supported for the given Entity
+ * @throws EntityNotSupportedException If the method is not supported for the given Entity
*/
public function listPartsURL(DBElement $entity): string
{
diff --git a/src/Services/ToolsTreeBuilder.php b/src/Services/ToolsTreeBuilder.php
index 3d06aa63..9e9b9e76 100644
--- a/src/Services/ToolsTreeBuilder.php
+++ b/src/Services/ToolsTreeBuilder.php
@@ -30,6 +30,7 @@
namespace App\Services;
use App\Entity\Attachments\AttachmentType;
+use App\Entity\Attachments\PartAttachment;
use App\Entity\Devices\Device;
use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
@@ -50,6 +51,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
/**
* This Service generates the tree structure for the tools.
+ * Whenever you change something here, you has to clear the cache, because the results are cached for performance reasons.
* @package App\Services
*/
class ToolsTreeBuilder
@@ -78,7 +80,7 @@ class ToolsTreeBuilder
/**
* Generates the tree for the tools menu.
* The result is cached.
- * @return TreeViewNode The array containing all Nodes for the tools menu.
+ * @return TreeViewNode[] The array containing all Nodes for the tools menu.
*/
public function getTree() : array
{
@@ -175,10 +177,18 @@ class ToolsTreeBuilder
protected function getShowNodes() : array
{
$show_nodes = array();
- $show_nodes[] = new TreeViewNode($this->translator->trans('tree.tools.show.all_parts'),
+ $show_nodes[] = new TreeViewNode(
+ $this->translator->trans('tree.tools.show.all_parts'),
$this->urlGenerator->generate('parts_show_all')
);
+ if ($this->security->isGranted('read', new PartAttachment())) {
+ $show_nodes[] = new TreeViewNode(
+ $this->translator->trans('tree.tools.show.all_attachments'),
+ $this->urlGenerator->generate('attachment_list')
+ );
+ }
+
return $show_nodes;
}
diff --git a/src/Services/TreeBuilder.php b/src/Services/TreeBuilder.php
index eb7f5f21..c45261af 100644
--- a/src/Services/TreeBuilder.php
+++ b/src/Services/TreeBuilder.php
@@ -80,7 +80,7 @@ class TreeBuilder
* @param DBElement|null $selectedElement When a element is given here, its tree node will be marked as selected in
* the resulting tree. When $selectedElement is not existing in the tree, then nothing happens.
* @return TreeViewNode The Node for the given Element.
- * @throws \App\Exceptions\EntityNotSupported
+ * @throws \App\Exceptions\EntityNotSupportedException
*/
public function elementToTreeNode(NamedDBElement $element, ?string $href_type = 'list_parts', DBElement $selectedElement = null) : TreeViewNode
{
@@ -123,7 +123,7 @@ class TreeBuilder
* @param DBElement|null $selectedElement When a element is given here, its tree node will be marked as selected in
* the resulting tree. When $selectedElement is not existing in the tree, then nothing happens.
* @return TreeViewNode[] Returns an array, containing all nodes. It is empty if the given class has no elements.
- * @throws \App\Exceptions\EntityNotSupported
+ * @throws \App\Exceptions\EntityNotSupportedException
*/
public function typeToTree(string $class_name, ?string $href_type = 'list_parts', DBElement $selectedElement = null) : array
{
diff --git a/templates/attachment_list.html.twig b/templates/attachment_list.html.twig
new file mode 100644
index 00000000..a198d6ee
--- /dev/null
+++ b/templates/attachment_list.html.twig
@@ -0,0 +1,16 @@
+{% extends "base.html.twig" %}
+
+{% block title %}{% trans %}attachment.list.title{% endtrans %}{% endblock %}
+
+{% block content %}
+