diff --git a/src/Controller/LabelController.php b/src/Controller/LabelController.php index 1f636cc2..53339b64 100644 --- a/src/Controller/LabelController.php +++ b/src/Controller/LabelController.php @@ -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); } } \ No newline at end of file diff --git a/src/Form/LabelSystem/LabelDialogType.php b/src/Form/LabelSystem/LabelDialogType.php index 9333228c..b1d59fff 100644 --- a/src/Form/LabelSystem/LabelDialogType.php +++ b/src/Form/LabelSystem/LabelDialogType.php @@ -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, [ diff --git a/src/Repository/DBElementRepository.php b/src/Repository/DBElementRepository.php index 41d8c058..0c84f1e5 100644 --- a/src/Repository/DBElementRepository.php +++ b/src/Repository/DBElementRepository.php @@ -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)); diff --git a/src/Services/LabelSystem/LabelGenerator.php b/src/Services/LabelSystem/LabelGenerator.php index e4ad77c7..c65e87bc 100644 --- a/src/Services/LabelSystem/LabelGenerator.php +++ b/src/Services/LabelSystem/LabelGenerator.php @@ -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(); } diff --git a/src/Services/LabelSystem/LabelHTMLGenerator.php b/src/Services/LabelSystem/LabelHTMLGenerator.php index d7f6b480..4b8bcf2f 100644 --- a/src/Services/LabelSystem/LabelHTMLGenerator.php +++ b/src/Services/LabelSystem/LabelHTMLGenerator.php @@ -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, ]); } diff --git a/src/Services/Misc/RangeParser.php b/src/Services/Misc/RangeParser.php new file mode 100644 index 00000000..ed5c4948 --- /dev/null +++ b/src/Services/Misc/RangeParser.php @@ -0,0 +1,94 @@ +. + */ + +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; + } +} \ No newline at end of file diff --git a/src/Validator/Constraints/Misc/ValidRange.php b/src/Validator/Constraints/Misc/ValidRange.php new file mode 100644 index 00000000..9f7e33d5 --- /dev/null +++ b/src/Validator/Constraints/Misc/ValidRange.php @@ -0,0 +1,33 @@ +. + */ + +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'; +} \ No newline at end of file diff --git a/src/Validator/Constraints/Misc/ValidRangeValidator.php b/src/Validator/Constraints/Misc/ValidRangeValidator.php new file mode 100644 index 00000000..17d36855 --- /dev/null +++ b/src/Validator/Constraints/Misc/ValidRangeValidator.php @@ -0,0 +1,61 @@ +. + */ + +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(); + } + } +} \ No newline at end of file diff --git a/templates/LabelSystem/labels/base_label.html.twig b/templates/LabelSystem/labels/base_label.html.twig index 3eb3249d..138e5ab0 100644 --- a/templates/LabelSystem/labels/base_label.html.twig +++ b/templates/LabelSystem/labels/base_label.html.twig @@ -11,6 +11,11 @@
- {{ lines | raw }} + {% for element in elements %} + {% include "LabelSystem/labels/label_page.html.twig" %} + {% if not loop.last %} + + {% endif %} + {% endfor %}