mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-28 04:30:08 +02:00
Allow to generate multiple labels at once (multiple pages in 1 PDF file).
This commit is contained in:
parent
4c5820ee22
commit
e89cc4bb01
12 changed files with 389 additions and 228 deletions
|
@ -29,8 +29,10 @@ use App\Entity\Parts\Part;
|
|||
use App\Form\LabelOptionsType;
|
||||
use App\Form\LabelSystem\LabelDialogType;
|
||||
use App\Helpers\LabelResponse;
|
||||
use App\Repository\DBElementRepository;
|
||||
use App\Services\ElementTypeNameGenerator;
|
||||
use App\Services\LabelSystem\LabelGenerator;
|
||||
use App\Services\Misc\RangeParser;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\SubmitButton;
|
||||
|
@ -49,12 +51,14 @@ class LabelController extends AbstractController
|
|||
protected $labelGenerator;
|
||||
protected $em;
|
||||
protected $elementTypeNameGenerator;
|
||||
protected $rangeParser;
|
||||
|
||||
public function __construct(LabelGenerator $labelGenerator, EntityManagerInterface $em, ElementTypeNameGenerator $elementTypeNameGenerator)
|
||||
public function __construct(LabelGenerator $labelGenerator, EntityManagerInterface $em, ElementTypeNameGenerator $elementTypeNameGenerator, RangeParser $rangeParser)
|
||||
{
|
||||
$this->labelGenerator = $labelGenerator;
|
||||
$this->em = $em;
|
||||
$this->elementTypeNameGenerator = $elementTypeNameGenerator;
|
||||
$this->rangeParser = $rangeParser;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,8 +94,8 @@ class LabelController extends AbstractController
|
|||
if ($profile === null && is_string($target_type)) {
|
||||
$label_options->setSupportedElement($target_type);
|
||||
}
|
||||
if (is_numeric($target_id)) {
|
||||
$form['target_id']->setData((int) $target_id);
|
||||
if (is_string($target_id)) {
|
||||
$form['target_id']->setData($target_id);
|
||||
}
|
||||
|
||||
|
||||
|
@ -105,10 +109,10 @@ class LabelController extends AbstractController
|
|||
$filename = 'invalid.pdf';
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$target_id = (int) $form->get('target_id')->getData();
|
||||
$target = $this->findObject($form_options->getSupportedElement(), $target_id);
|
||||
$pdf_data = $this->labelGenerator->generateLabel($form_options, $target);
|
||||
$filename = $this->getLabelName($target, $profile);
|
||||
$target_id = (string) $form->get('target_id')->getData();
|
||||
$targets = $this->findObjects($form_options->getSupportedElement(), $target_id);
|
||||
$pdf_data = $this->labelGenerator->generateLabel($form_options, $targets);
|
||||
$filename = $this->getLabelName($targets[0], $profile);
|
||||
}
|
||||
|
||||
return $this->render('LabelSystem/dialog.html.twig', [
|
||||
|
@ -126,11 +130,16 @@ class LabelController extends AbstractController
|
|||
return $ret . '.pdf';
|
||||
}
|
||||
|
||||
protected function findObject(string $type, int $id): object
|
||||
protected function findObjects(string $type, string $ids): array
|
||||
{
|
||||
if(!isset(LabelGenerator::CLASS_SUPPORT_MAPPING[$type])) {
|
||||
throw new \InvalidArgumentException('The given type is not known and can not be mapped to a class!');
|
||||
}
|
||||
return $this->em->find(LabelGenerator::CLASS_SUPPORT_MAPPING[$type], $id);
|
||||
|
||||
$id_array = $this->rangeParser->parse($ids);
|
||||
|
||||
/** @var DBElementRepository $repo */
|
||||
$repo = $this->em->getRepository(LabelGenerator::CLASS_SUPPORT_MAPPING[$type]);
|
||||
return $repo->getElementsFromIDArray($id_array);
|
||||
}
|
||||
}
|
|
@ -22,22 +22,25 @@ namespace App\Form\LabelSystem;
|
|||
|
||||
|
||||
use App\Form\LabelOptionsType;
|
||||
use App\Validator\Constraints\Misc\ValidRange;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Type;
|
||||
|
||||
class LabelDialogType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add('target_id', NumberType::class, [
|
||||
'html5' => true,
|
||||
$builder->add('target_id', TextType::class, [
|
||||
'required' => true,
|
||||
'label' => 'label_generator.target_id.label'
|
||||
'label' => 'label_generator.target_id.label',
|
||||
'help' => 'label_generator.target_id.range_hint',
|
||||
'constraints' => [
|
||||
new ValidRange(),
|
||||
],
|
||||
]);
|
||||
|
||||
$builder->add('options', LabelOptionsType::class, [
|
||||
|
|
|
@ -50,6 +50,22 @@ class DBElementRepository extends EntityRepository
|
|||
$this->setField($element, 'id', $new_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all elements that match a list of IDs.
|
||||
* @param array $ids
|
||||
* @return AbstractDBElement[]
|
||||
*/
|
||||
public function getElementsFromIDArray(array $ids): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('element');
|
||||
$q = $qb->select('element')
|
||||
->where('element.id IN (?1)')
|
||||
->setParameter(1, $ids)
|
||||
->getQuery();
|
||||
|
||||
return $q->getResult();
|
||||
}
|
||||
|
||||
protected function setField(AbstractDBElement $element, string $field, int $new_value): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(get_class($element));
|
||||
|
|
|
@ -45,17 +45,35 @@ class LabelGenerator
|
|||
$this->labelHTMLGenerator = $labelHTMLGenerator;
|
||||
}
|
||||
|
||||
public function generateLabel(LabelOptions $options, object $element): string
|
||||
/**
|
||||
* @param LabelOptions $options
|
||||
* @param object|object[] $element An element or an array of elements for which labels should be generated
|
||||
* @return string
|
||||
*/
|
||||
public function generateLabel(LabelOptions $options, $elements): string
|
||||
{
|
||||
if (!$this->supports($options, $element)) {
|
||||
throw new \InvalidArgumentException('The given options are not compatible with the given element!');
|
||||
if (!is_array($elements) && !is_object($elements)) {
|
||||
throw new \InvalidArgumentException('$element must be an object or an array of objects!');
|
||||
}
|
||||
|
||||
if (!is_array($elements)) {
|
||||
$elements = [$elements];
|
||||
}
|
||||
|
||||
$elements_html = [];
|
||||
|
||||
dump($elements);
|
||||
|
||||
foreach ($elements as $element) {
|
||||
if (!$this->supports($options, $element)) {
|
||||
throw new \InvalidArgumentException('The given options are not compatible with the given element!');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$dompdf = new Dompdf();
|
||||
$dompdf->loadHtml($this->labelHTMLGenerator->getLabelHTML($options, $element));
|
||||
|
||||
$dompdf->setPaper($this->mmToPointsArray($options->getWidth(), $options->getHeight()));
|
||||
|
||||
$dompdf->loadHtml($this->labelHTMLGenerator->getLabelHTML($options, $elements));
|
||||
$dompdf->render();
|
||||
return $dompdf->output();
|
||||
}
|
||||
|
|
|
@ -38,11 +38,23 @@ class LabelHTMLGenerator
|
|||
$this->replacer = $replacer;
|
||||
}
|
||||
|
||||
public function getLabelHTML(LabelOptions $options, object $element): string
|
||||
public function getLabelHTML(LabelOptions $options, array $elements): string
|
||||
{
|
||||
if (empty($elements)) {
|
||||
throw new \InvalidArgumentException('$elements must not be empty');
|
||||
}
|
||||
|
||||
$twig_elements = [];
|
||||
foreach ($elements as $element) {
|
||||
$twig_elements[] = [
|
||||
'element' => $element,
|
||||
'lines' => $this->replacer->replace($options->getLines(), $element)
|
||||
];
|
||||
}
|
||||
|
||||
return $this->twig->render('LabelSystem/labels/base_label.html.twig', [
|
||||
'meta_title' => $this->getPDFTitle($options, $element),
|
||||
'lines' => $this->replacer->replace($options->getLines(), $element),
|
||||
'meta_title' => $this->getPDFTitle($options, $elements[0]),
|
||||
'elements' => $twig_elements,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
94
src/Services/Misc/RangeParser.php
Normal file
94
src/Services/Misc/RangeParser.php
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Services\Misc;
|
||||
|
||||
/**
|
||||
* This Parser allows to parse number ranges like 1-3, 4, 5
|
||||
*/
|
||||
class RangeParser
|
||||
{
|
||||
/**
|
||||
* Converts the given range string to an array of all numbers in the given range.
|
||||
* @param string $range A range string like '1-3, 5, 6'
|
||||
* @return int[] An array with all numbers from the range (e.g. [1, 2, 3, 5, 6]
|
||||
*/
|
||||
public function parse(string $range_str): array
|
||||
{
|
||||
//Normalize number separator (we allow , and ;):
|
||||
$range_str = str_replace(';', ',', $range_str);
|
||||
|
||||
$numbers = explode(',', $range_str);
|
||||
$ranges = [];
|
||||
foreach ($numbers as $number) {
|
||||
$number = trim($number);
|
||||
//Extract min / max if token is a range
|
||||
$matches = [];
|
||||
if (preg_match('/^(-?\s*\d+)\s*-\s*(-?\s*\d+)$/', $number, $matches)) {
|
||||
$ranges[] = $this->generateMinMaxRange($matches[1], $matches[2]);
|
||||
} elseif (is_numeric($number)) {
|
||||
$ranges[] = [(int) $number];
|
||||
} elseif (empty($number)) { //Allow empty tokens
|
||||
continue;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid range encoutered: ' . $number);
|
||||
}
|
||||
}
|
||||
|
||||
//Flatten ranges array
|
||||
return array_merge([], ...$ranges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given string is a valid range.
|
||||
* @param string $range_str The string that should be checked
|
||||
* @return bool True if the string is valid, false if not.
|
||||
*/
|
||||
public function isValidRange(string $range_str): bool
|
||||
{
|
||||
try {
|
||||
$this->parse($range_str);
|
||||
return true;
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected function generateMinMaxRange(string $min, string $max): array
|
||||
{
|
||||
$min = (int) $min;
|
||||
$max = (int) $max;
|
||||
|
||||
//Ensure that $max > $min
|
||||
if ($min > $max) {
|
||||
$a = $max;
|
||||
$max = $min;
|
||||
$min = $a;
|
||||
}
|
||||
|
||||
$tmp = [];
|
||||
while ($min <= $max) {
|
||||
$tmp[] = $min;
|
||||
$min++;
|
||||
};
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
}
|
33
src/Validator/Constraints/Misc/ValidRange.php
Normal file
33
src/Validator/Constraints/Misc/ValidRange.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Validator\Constraints\Misc;
|
||||
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @package App\Validator\Constraints\Misc
|
||||
*/
|
||||
class ValidRange extends Constraint
|
||||
{
|
||||
public $message = 'validator.invalid_range';
|
||||
}
|
61
src/Validator/Constraints/Misc/ValidRangeValidator.php
Normal file
61
src/Validator/Constraints/Misc/ValidRangeValidator.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Validator\Constraints\Misc;
|
||||
|
||||
|
||||
use App\Services\Misc\RangeParser;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedValueException;
|
||||
|
||||
class ValidRangeValidator extends ConstraintValidator
|
||||
{
|
||||
|
||||
protected $rangeParser;
|
||||
|
||||
public function __construct(RangeParser $rangeParser)
|
||||
{
|
||||
$this->rangeParser = $rangeParser;
|
||||
}
|
||||
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (!$constraint instanceof ValidRange) {
|
||||
throw new UnexpectedTypeException($constraint, ValidRange::class);
|
||||
}
|
||||
|
||||
// custom constraints should ignore null and empty values to allow
|
||||
// other constraints (NotBlank, NotNull, etc.) take care of that
|
||||
if (null === $value || '' === $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_string($value)) {
|
||||
throw new UnexpectedValueException($value, 'string');
|
||||
}
|
||||
|
||||
if(!$this->rangeParser->isValidRange($value)) {
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue