diff --git a/src/DataTables/AttachmentDataTable.php b/src/DataTables/AttachmentDataTable.php
index 16e6a7a7..6c4c905a 100644
--- a/src/DataTables/AttachmentDataTable.php
+++ b/src/DataTables/AttachmentDataTable.php
@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace App\DataTables;
+use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\PrettyBoolColumn;
use App\DataTables\Column\RowClassColumn;
@@ -40,14 +41,19 @@ use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableTypeInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
-final class AttachmentDataTable implements DataTableTypeInterface
+final readonly class AttachmentDataTable implements DataTableTypeInterface
{
- public function __construct(private readonly TranslatorInterface $translator, private readonly EntityURLGenerator $entityURLGenerator, private readonly AttachmentManager $attachmentHelper, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly ElementTypeNameGenerator $elementTypeNameGenerator)
+ public function __construct(private TranslatorInterface $translator, private EntityURLGenerator $entityURLGenerator, private AttachmentManager $attachmentHelper, private AttachmentURLGenerator $attachmentURLGenerator, private ElementTypeNameGenerator $elementTypeNameGenerator)
{
}
public function configure(DataTable $dataTable, array $options): void
{
+ /*************************************************************************************************************
+ * Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
+ * HTMLColumn, if necessary
+ ************************************************************************************************************/
+
$dataTable->add('dont_matter', RowClassColumn::class, [
'render' => function ($value, Attachment $context): string {
//Mark attachments yellow which have an internal file linked that doesn't exist
@@ -59,10 +65,10 @@ final class AttachmentDataTable implements DataTableTypeInterface
},
]);
- $dataTable->add('picture', TextColumn::class, [
+ $dataTable->add('picture', HTMLColumn::class, [
'label' => '',
'className' => 'no-colvis',
- 'render' => function ($value, Attachment $context): string {
+ 'data' => function (Attachment $context): string {
if ($context->isPicture()
&& $this->attachmentHelper->isInternalFileExisting($context)) {
@@ -95,65 +101,65 @@ final class AttachmentDataTable implements DataTableTypeInterface
'orderField' => 'NATSORT(attachment.name)',
]);
- $dataTable->add('attachment_type', TextColumn::class, [
+ $dataTable->add('attachment_type', HTMLColumn::class, [
'label' => 'attachment.table.type',
'field' => 'attachment_type.name',
'orderField' => 'NATSORT(attachment_type.name)',
- 'render' => fn($value, Attachment $context): string => sprintf(
+ 'data' => fn(Attachment $context, $value): string => sprintf(
'%s',
$this->entityURLGenerator->editURL($context->getAttachmentType()),
htmlspecialchars((string) $value)
),
]);
- $dataTable->add('element', TextColumn::class, [
+ $dataTable->add('element', HTMLColumn::class, [
'label' => 'attachment.table.element',
//'propertyPath' => 'element.name',
- 'render' => fn($value, Attachment $context): string => sprintf(
+ 'data' => fn(Attachment $context): string => sprintf(
'%s',
$this->entityURLGenerator->infoURL($context->getElement()),
$this->elementTypeNameGenerator->getTypeNameCombination($context->getElement(), true)
),
]);
- $dataTable->add('internal_link', TextColumn::class, [
+ $dataTable->add('internal_link', HTMLColumn::class, [
'label' => 'attachment.table.internal_file',
'propertyPath' => 'filename',
'orderField' => 'NATSORT(attachment.original_filename)',
- 'render' => function ($value, Attachment $context) {
+ 'data' => function (Attachment $context, $value) {
if ($this->attachmentHelper->isInternalFileExisting($context)) {
return sprintf(
'%s',
$this->entityURLGenerator->viewURL($context),
- htmlspecialchars($value)
+ htmlspecialchars((string) $value)
);
}
- return $value;
- }
+ return htmlspecialchars((string) $value);
+ },
]);
- $dataTable->add('external_link', TextColumn::class, [
+ $dataTable->add('external_link', HTMLColumn::class, [
'label' => 'attachment.table.external_link',
'propertyPath' => 'host',
'orderField' => 'attachment.external_path',
- 'render' => function ($value, Attachment $context) {
+ 'data' => function (Attachment $context, $value) {
if ($context->hasExternal()) {
return sprintf(
'%s',
htmlspecialchars((string) $context->getExternalPath()),
htmlspecialchars((string) $context->getExternalPath()),
- htmlspecialchars($value),
+ htmlspecialchars((string) $value),
);
}
- return $value;
- }
+ return htmlspecialchars((string) $value);
+ },
]);
- $dataTable->add('filesize', TextColumn::class, [
+ $dataTable->add('filesize', HTMLColumn::class, [
'label' => $this->translator->trans('attachment.table.filesize'),
- 'render' => function ($value, Attachment $context) {
+ 'data' => function (Attachment $context) {
if (!$context->hasInternal()) {
return sprintf(
'
@@ -168,7 +174,7 @@ final class AttachmentDataTable implements DataTableTypeInterface
'
%s
',
- $this->attachmentHelper->getHumanFileSize($context)
+ htmlspecialchars($this->attachmentHelper->getHumanFileSize($context))
);
}
diff --git a/src/DataTables/Column/HTMLColumn.php b/src/DataTables/Column/HTMLColumn.php
new file mode 100644
index 00000000..a1220dd3
--- /dev/null
+++ b/src/DataTables/Column/HTMLColumn.php
@@ -0,0 +1,37 @@
+.
+ */
+namespace App\DataTables\Column;
+
+use Omines\DataTablesBundle\Column\TextColumn;
+
+/**
+ * A TextColumn whose value is always treated as raw HTML and therefore never passed through htmlspecialchars().
+ * The value returned by the 'data' option must already contain properly escaped/sanitized HTML, as it is output as-is.
+ */
+class HTMLColumn extends TextColumn
+{
+ public function isRaw(): bool
+ {
+ return true;
+ }
+}
diff --git a/src/DataTables/ErrorDataTable.php b/src/DataTables/ErrorDataTable.php
index 833ea934..a16b453e 100644
--- a/src/DataTables/ErrorDataTable.php
+++ b/src/DataTables/ErrorDataTable.php
@@ -22,9 +22,9 @@ declare(strict_types=1);
*/
namespace App\DataTables;
+use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\RowClassColumn;
use Omines\DataTablesBundle\Adapter\ArrayAdapter;
-use Omines\DataTablesBundle\Column\TextColumn;
use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableFactory;
use Omines\DataTablesBundle\DataTableTypeInterface;
@@ -32,7 +32,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
-class ErrorDataTable implements DataTableTypeInterface
+final readonly class ErrorDataTable implements DataTableTypeInterface
{
public function configureOptions(OptionsResolver $optionsResolver): void
{
@@ -49,6 +49,11 @@ class ErrorDataTable implements DataTableTypeInterface
public function configure(DataTable $dataTable, array $options): void
{
+ /*************************************************************************************************************
+ * Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
+ * HTMLColumn, if necessary
+ ************************************************************************************************************/
+
$optionsResolver = new OptionsResolver();
$this->configureOptions($optionsResolver);
$options = $optionsResolver->resolve($options);
@@ -58,9 +63,9 @@ class ErrorDataTable implements DataTableTypeInterface
'render' => fn($value, $context): string => 'table-warning',
])
- ->add('error', TextColumn::class, [
+ ->add('error', HTMLColumn::class, [
'label' => 'error_table.error',
- 'render' => fn($value, $context): string => ' ' . $value,
+ 'data' => fn($context, $value): string => ' ' . htmlspecialchars((string) $value),
])
;
diff --git a/src/DataTables/Helpers/PartDataTableHelper.php b/src/DataTables/Helpers/PartDataTableHelper.php
index 54094ff1..2f40dbd2 100644
--- a/src/DataTables/Helpers/PartDataTableHelper.php
+++ b/src/DataTables/Helpers/PartDataTableHelper.php
@@ -62,7 +62,7 @@ class PartDataTableHelper
}
if ($context->getBuiltProject() instanceof Project) {
$icon = sprintf('',
- $this->translator->trans('part.info.projectBuildPart.hint').': '.$context->getBuiltProject()->getName());
+ $this->translator->trans('part.info.projectBuildPart.hint').': '.htmlspecialchars($context->getBuiltProject()->getName()));
}
diff --git a/src/DataTables/LogDataTable.php b/src/DataTables/LogDataTable.php
index 2c37767b..5c4ca88b 100644
--- a/src/DataTables/LogDataTable.php
+++ b/src/DataTables/LogDataTable.php
@@ -25,6 +25,7 @@ namespace App\DataTables;
use App\DataTables\Column\EnumColumn;
use App\Entity\LogSystem\LogTargetType;
use Symfony\Bundle\SecurityBundle\Security;
+use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\IconLinkColumn;
use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\LogEntryExtraColumn;
@@ -59,7 +60,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
-class LogDataTable implements DataTableTypeInterface
+final readonly class LogDataTable implements DataTableTypeInterface
{
protected LogEntryRepository $logRepo;
@@ -95,6 +96,11 @@ class LogDataTable implements DataTableTypeInterface
public function configure(DataTable $dataTable, array $options): void
{
+ /*************************************************************************************************************
+ * Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
+ * HTMLColumn, if necessary
+ ************************************************************************************************************/
+
$resolver = new OptionsResolver();
$this->configureOptions($resolver);
$options = $resolver->resolve($options);
@@ -104,10 +110,10 @@ class LogDataTable implements DataTableTypeInterface
'render' => fn($value, AbstractLogEntry $context) => $this->logLevelHelper->logLevelToTableColorClass($context->getLevelString()),
]);
- $dataTable->add('symbol', TextColumn::class, [
+ $dataTable->add('symbol', HTMLColumn::class, [
'label' => '',
'className' => 'no-colvis',
- 'render' => fn($value, AbstractLogEntry $context): string => sprintf(
+ 'data' => fn(AbstractLogEntry $context): string => sprintf(
'',
$this->logLevelHelper->logLevelToIconClass($context->getLevelString()),
$context->getLevelString()
@@ -128,10 +134,10 @@ class LogDataTable implements DataTableTypeInterface
)
]);
- $dataTable->add('type', TextColumn::class, [
+ $dataTable->add('type', HTMLColumn::class, [
'label' => 'log.type',
'propertyPath' => 'type',
- 'render' => function (string $value, AbstractLogEntry $context) {
+ 'data' => function (AbstractLogEntry $context, string $value) {
$text = $this->translator->trans('log.type.'.$value);
if ($context instanceof PartStockChangedLogEntry) {
@@ -149,20 +155,20 @@ class LogDataTable implements DataTableTypeInterface
'label' => 'log.level',
'visible' => 'system_log' === $options['mode'],
'propertyPath' => 'levelString',
- 'render' => fn(string $value, AbstractLogEntry $context) => $this->translator->trans('log.level.'.$value),
+ 'data' => fn(AbstractLogEntry $context, string $value) => $this->translator->trans('log.level.'.$value),
]);
- $dataTable->add('user', TextColumn::class, [
+ $dataTable->add('user', HTMLColumn::class, [
'label' => 'log.user',
'orderField' => 'NATSORT(user.name)',
- 'render' => function ($value, AbstractLogEntry $context): string {
+ 'data' => function (AbstractLogEntry $context): string {
$user = $context->getUser();
//If user was deleted, show the info from the username field
if (!$user instanceof User) {
if ($context->isCLIEntry()) {
return sprintf('%s [%s]',
- htmlentities((string) $context->getCLIUsername()),
+ htmlspecialchars((string) $context->getCLIUsername()),
$this->translator->trans('log.cli_user')
);
}
@@ -170,7 +176,7 @@ class LogDataTable implements DataTableTypeInterface
//Else we just deal with a deleted user
return sprintf(
'@%s [%s]',
- htmlentities($context->getUsername()),
+ htmlspecialchars($context->getUsername()),
$this->translator->trans('log.target_deleted'),
);
}
@@ -182,7 +188,7 @@ class LogDataTable implements DataTableTypeInterface
$img_url,
$this->userAvatarHelper->getAvatarMdURL($user),
$this->urlGenerator->generate('user_info', ['id' => $user->getID()]),
- htmlentities($user->getFullName(true))
+ htmlspecialchars($user->getFullName(true))
);
},
]);
@@ -194,7 +200,7 @@ class LogDataTable implements DataTableTypeInterface
'render' => function (LogTargetType $value, AbstractLogEntry $context) {
$class = $value->toClass();
if (null !== $class) {
- return $this->elementTypeNameGenerator->getLocalizedTypeLabel($class);
+ return $this->elementTypeNameGenerator->typeLabel($class);
}
return '';
@@ -216,9 +222,9 @@ class LogDataTable implements DataTableTypeInterface
'icon' => 'fas fa-fw fa-eye',
'href' => function ($value, AbstractLogEntry $context) {
if (
+ $context instanceof CollectionElementDeleted ||
($context instanceof TimeTravelInterface
&& $context->hasOldDataInformation())
- || $context instanceof CollectionElementDeleted
) {
try {
$target = $this->logRepo->getTargetElement($context);
diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php
index bcf64056..5912a20e 100644
--- a/src/DataTables/PartsDataTable.php
+++ b/src/DataTables/PartsDataTable.php
@@ -25,6 +25,7 @@ namespace App\DataTables;
use App\DataTables\Adapters\TwoStepORMAdapter;
use App\DataTables\Column\EntityColumn;
use App\DataTables\Column\EnumColumn;
+use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\IconLinkColumn;
use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\MarkdownColumn;
@@ -58,7 +59,7 @@ use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;
-final class PartsDataTable implements DataTableTypeInterface
+final readonly class PartsDataTable implements DataTableTypeInterface
{
public const LENGTH_MENU = [[10, 25, 50, 100, 250, 500, -1], [10, 25, 50, 100, 250, 500, "All"]];
@@ -94,6 +95,11 @@ final class PartsDataTable implements DataTableTypeInterface
* When adding columns here, add them also to PartTableColumns enum, to make them configurable in the settings!
*************************************************************************************************************/
+ /*************************************************************************************************************
+ * Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
+ * HTMLColumn, if necessary
+ ************************************************************************************************************/
+
$this->csh
//Color the table rows depending on the review and favorite status
->add('row_color', RowClassColumn::class, [
@@ -109,23 +115,23 @@ final class PartsDataTable implements DataTableTypeInterface
},
], visibility_configurable: false)
->add('select', SelectColumn::class, visibility_configurable: false)
- ->add('picture', TextColumn::class, [
+ ->add('picture', HTMLColumn::class, [
'label' => '',
'className' => 'no-colvis',
- 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderPicture($context),
+ 'data' => fn(Part $context) => $this->partDataTableHelper->renderPicture($context),
], visibility_configurable: false)
- ->add('name', TextColumn::class, [
+ ->add('name', HTMLColumn::class, [
'label' => $this->translator->trans('part.table.name'),
- 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderName($context),
+ 'data' => fn(Part $context) => $this->partDataTableHelper->renderName($context),
'orderField' => 'NATSORT(part.name)'
])
->add('si_value', TextColumn::class, [
'label' => $this->translator->trans('part.table.si_value'),
- 'render' => function ($value, Part $context): string {
+ 'data' => function (Part $context): string {
$siValue = SiValueSort::sqliteSiValue($context->getName());
if ($siValue !== null) {
//Output it as scientific number with a big E
- return htmlspecialchars(sprintf('%G', $siValue));
+ return sprintf('%G', $siValue);
}
return '';
},
@@ -156,38 +162,38 @@ final class PartsDataTable implements DataTableTypeInterface
'label' => $this->translator->trans('part.table.manufacturer'),
'orderField' => 'NATSORT(_manufacturer.name)'
])
- ->add('storelocation', TextColumn::class, [
+ ->add('storelocation', HTMLColumn::class, [
'label' => $this->translator->trans('part.table.storeLocations'),
//We need to use a aggregate function to get the first store location, as we have a one-to-many relation
'orderField' => 'NATSORT(MIN(_storelocations.name))',
- 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context),
+ 'data' => fn(Part $context) => $this->partDataTableHelper->renderStorageLocations($context),
], alias: 'storage_location')
- ->add('amount', TextColumn::class, [
+ ->add('amount', HTMLColumn::class, [
'label' => $this->translator->trans('part.table.amount'),
- 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderAmount($context),
+ 'data' => fn(Part $context) => $this->partDataTableHelper->renderAmount($context),
'orderField' => 'amountSum'
])
->add('minamount', TextColumn::class, [
'label' => $this->translator->trans('part.table.minamount'),
- 'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format(
+ 'data' => fn(Part $context, $value): string => $this->amountFormatter->format(
$value,
$context->getPartUnit()
- )),
+ ),
])
->add('partUnit', TextColumn::class, [
'label' => $this->translator->trans('part.table.partUnit'),
'orderField' => 'NATSORT(_partUnit.name)',
- 'render' => function ($value, Part $context): string {
+ 'data' => function (Part $context): string {
$partUnit = $context->getPartUnit();
if ($partUnit === null) {
return '';
}
- $tmp = htmlspecialchars($partUnit->getName());
+ $tmp = $partUnit->getName();
if ($partUnit->getUnit()) {
- $tmp .= ' (' . htmlspecialchars($partUnit->getUnit()) . ')';
+ $tmp .= ' (' . $partUnit->getUnit() . ')';
}
return $tmp;
}
@@ -195,14 +201,14 @@ final class PartsDataTable implements DataTableTypeInterface
->add('partCustomState', TextColumn::class, [
'label' => $this->translator->trans('part.table.partCustomState'),
'orderField' => 'NATSORT(_partCustomState.name)',
- 'render' => function($value, Part $context): string {
+ 'data' => function(Part $context): string {
$partCustomState = $context->getPartCustomState();
if ($partCustomState === null) {
return '';
}
- return htmlspecialchars($partCustomState->getName());
+ return $partCustomState->getName();
}
])
->add('addedDate', LocaleDateTimeColumn::class, [
@@ -248,25 +254,25 @@ final class PartsDataTable implements DataTableTypeInterface
])
->add('eda_reference', TextColumn::class, [
'label' => $this->translator->trans('part.table.eda_reference'),
- 'render' => static fn($value, Part $context) => htmlspecialchars($context->getEdaInfo()->getReferencePrefix() ?? ''),
+ 'data' => static fn(Part $context) => $context->getEdaInfo()->getReferencePrefix() ?? '',
'orderField' => 'NATSORT(part.eda_info.reference_prefix)'
])
->add('eda_value', TextColumn::class, [
'label' => $this->translator->trans('part.table.eda_value'),
- 'render' => static fn($value, Part $context) => htmlspecialchars($context->getEdaInfo()->getValue() ?? ''),
+ 'data' => static fn(Part $context) => $context->getEdaInfo()->getValue() ?? '',
'orderField' => 'NATSORT(part.eda_info.value)'
])
- ->add('eda_status', TextColumn::class, [
+ ->add('eda_status', HTMLColumn::class, [
'label' => $this->translator->trans('part.table.eda_status'),
- 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderEdaStatus($context),
+ 'data' => fn(Part $context) => $this->partDataTableHelper->renderEdaStatus($context),
'className' => 'text-center',
]);
//Add a column to list the projects where the part is used, when the user has the permission to see the projects
if ($this->security->isGranted('read', Project::class)) {
- $this->csh->add('projects', TextColumn::class, [
+ $this->csh->add('projects', HTMLColumn::class, [
'label' => $this->translator->trans('project.labelp'),
- 'render' => function ($value, Part $context): string {
+ 'data' => function (Part $context): string {
//Only show the first 5 projects names
$projects = $context->getProjects();
$tmp = "";
@@ -286,7 +292,7 @@ final class PartsDataTable implements DataTableTypeInterface
}
return $tmp;
- }
+ },
]);
}
diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php
index b5beeca0..f65f0df7 100644
--- a/src/DataTables/ProjectBomEntriesDataTable.php
+++ b/src/DataTables/ProjectBomEntriesDataTable.php
@@ -25,6 +25,7 @@ namespace App\DataTables;
use App\DataTables\Adapters\TwoStepORMAdapter;
use App\DataTables\Column\EntityColumn;
use App\DataTables\Column\EnumColumn;
+use App\DataTables\Column\HTMLColumn;
use App\DataTables\Column\LocaleDateTimeColumn;
use App\DataTables\Column\MarkdownColumn;
use App\DataTables\Helpers\PartDataTableHelper;
@@ -48,7 +49,7 @@ use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableTypeInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
-class ProjectBomEntriesDataTable implements DataTableTypeInterface
+final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterface
{
public function __construct(
protected EntityURLGenerator $entityURLGenerator,
@@ -63,17 +64,22 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
public function configure(DataTable $dataTable, array $options): void
{
+ /*************************************************************************************************************
+ * Avoid using render, as it has no escaping, and is a potential security risk. Use data on TextColumn or the
+ * HTMLColumn, if necessary
+ ************************************************************************************************************/
+
$dataTable
//->add('select', SelectColumn::class)
- ->add('picture', TextColumn::class, [
+ ->add('picture', HTMLColumn::class, [
'label' => '',
'className' => 'no-colvis',
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
if(!$context->getPart() instanceof Part) {
return '';
}
return $this->partDataTableHelper->renderPicture($context->getPart());
- }
+ },
])
->add('id', TextColumn::class, [
@@ -85,27 +91,27 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
'label' => $this->translator->trans('project.bom.quantity'),
'className' => 'text-center',
'orderField' => 'bom_entry.quantity',
- 'render' => function ($value, ProjectBOMEntry $context): float|string {
+ 'data' => function (ProjectBOMEntry $context): float|string {
//If we have a non-part entry, only show the rounded quantity
if (!$context->getPart() instanceof Part) {
return round($context->getQuantity());
}
//Otherwise use the unit of the part to format the quantity
- return htmlspecialchars($this->amountFormatter->format($context->getQuantity(), $context->getPart()->getPartUnit()));
+ return $this->amountFormatter->format($context->getQuantity(), $context->getPart()->getPartUnit());
},
])
->add('partId', TextColumn::class, [
'label' => $this->translator->trans('project.bom.part_id'),
'visible' => true,
'orderField' => 'part.id',
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
return $context->getPart() instanceof Part ? (string) $context->getPart()->getId() : '';
},
])
- ->add('name', TextColumn::class, [
+ ->add('name', HTMLColumn::class, [
'label' => $this->translator->trans('part.table.name'),
'orderField' => 'NATSORT(part.name)',
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
if(!$context->getPart() instanceof Part) {
return htmlspecialchars((string) $context->getName());
}
@@ -123,11 +129,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
'label' => $this->translator->trans('part.table.ipn'),
'orderField' => 'NATSORT(part.ipn)',
'visible' => false,
- 'render' => function ($value, ProjectBOMEntry $context) {
- if($context->getPart() instanceof Part) {
- return $context->getPart()->getIpn();
- }
- }
+ 'data' => fn (ProjectBOMEntry $context) => $context->getPart()?->getIpn()
])
->add('description', MarkdownColumn::class, [
'label' => $this->translator->trans('part.table.description'),
@@ -172,9 +174,9 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
},
])
- ->add('mountnames', TextColumn::class, [
+ ->add('mountnames', HTMLColumn::class, [
'label' => 'project.bom.mountnames',
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
$html = '';
foreach (explode(',', $context->getMountnames()) as $mountname) {
@@ -184,34 +186,34 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
},
])
- ->add('instockAmount', TextColumn::class, [
+ ->add('instockAmount', HTMLColumn::class, [
'label' => 'project.bom.instockAmount',
'visible' => false,
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
if ($context->getPart() !== null) {
return $this->partDataTableHelper->renderAmount($context->getPart());
}
return '';
- }
+ },
])
- ->add('storelocation', TextColumn::class, [
+ ->add('storelocation', HTMLColumn::class, [
'label' => $this->translator->trans('part.table.storeLocations'),
//We need to use a aggregate function to get the first store location, as we have a one-to-many relation
'orderField' => 'NATSORT(MIN(_storelocations.name))',
'visible' => false,
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
if ($context->getPart() !== null) {
return $this->partDataTableHelper->renderStorageLocations($context->getPart());
}
return '';
- }
+ },
])
->add('price', TextColumn::class, [
'label' => 'project.bom.price',
'visible' => false,
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
$price = $this->projectBuildHelper->getEntryUnitPrice($context);
return $this->moneyFormatter->format($price->toScale(2, RoundingMode::Up)->toFloat(), null, 2, true);
},
@@ -219,7 +221,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
->add('ext_price', TextColumn::class, [
'label' => 'project.bom.ext_price',
'visible' => false,
- 'render' => function ($value, ProjectBOMEntry $context) {
+ 'data' => function (ProjectBOMEntry $context) {
$price = $this->projectBuildHelper->getEntryUnitPrice($context);
return $this->moneyFormatter->format(
$price->multipliedBy(BigDecimal::fromFloatShortest($context->getQuantity()))