diff --git a/assets/controllers/elements/attachment_autocomplete_controller.js b/assets/controllers/elements/attachment_autocomplete_controller.js index 94b01136..0175b284 100644 --- a/assets/controllers/elements/attachment_autocomplete_controller.js +++ b/assets/controllers/elements/attachment_autocomplete_controller.js @@ -34,11 +34,6 @@ export default class extends Controller { connect() { - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - let settings = { persistent: false, create: true, @@ -47,7 +42,7 @@ export default class extends Controller { selectOnTab: true, //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', - dropdownParent: dropdownParent, + dropdownParent: 'body', render: { item: (data, escape) => { return '' + escape(data.label) + ''; diff --git a/assets/controllers/elements/part_select_controller.js b/assets/controllers/elements/part_select_controller.js index 8a4e19b8..0658f4b4 100644 --- a/assets/controllers/elements/part_select_controller.js +++ b/assets/controllers/elements/part_select_controller.js @@ -10,19 +10,13 @@ export default class extends Controller { connect() { - //Check if tomselect is inside an modal and do not attach the dropdown to body in that case (as it breaks the modal) - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - let settings = { allowEmptyOption: true, plugins: ['dropdown_input'], searchField: ["name", "description", "category", "footprint"], valueField: "id", labelField: "name", - dropdownParent: dropdownParent, + dropdownParent: 'body', preload: "focus", render: { item: (data, escape) => { diff --git a/assets/controllers/elements/select_controller.js b/assets/controllers/elements/select_controller.js index d70e588c..f933731a 100644 --- a/assets/controllers/elements/select_controller.js +++ b/assets/controllers/elements/select_controller.js @@ -38,17 +38,13 @@ export default class extends Controller { this._emptyMessage = this.element.getAttribute('title'); } - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } let settings = { plugins: ["clear_button"], allowEmptyOption: true, selectOnTab: true, maxOptions: null, - dropdownParent: dropdownParent, + dropdownParent: 'body', render: { item: this.renderItem.bind(this), diff --git a/assets/controllers/elements/select_multiple_controller.js b/assets/controllers/elements/select_multiple_controller.js index 17e85fae..daa6b0a1 100644 --- a/assets/controllers/elements/select_multiple_controller.js +++ b/assets/controllers/elements/select_multiple_controller.js @@ -26,15 +26,10 @@ export default class extends Controller { _tomSelect; connect() { - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - this._tomSelect = new TomSelect(this.element, { maxItems: 1000, allowEmptyOption: true, - dropdownParent: dropdownParent, + dropdownParent: 'body', plugins: ['remove_button'], }); } diff --git a/assets/controllers/elements/static_file_autocomplete_controller.js b/assets/controllers/elements/static_file_autocomplete_controller.js index 9703c618..0421a26d 100644 --- a/assets/controllers/elements/static_file_autocomplete_controller.js +++ b/assets/controllers/elements/static_file_autocomplete_controller.js @@ -40,11 +40,6 @@ export default class extends Controller { connect() { - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - let settings = { persistent: false, create: true, @@ -55,7 +50,7 @@ export default class extends Controller { valueField: 'text', searchField: 'text', orderField: 'text', - dropdownParent: dropdownParent, + dropdownParent: 'body', //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', diff --git a/assets/controllers/elements/structural_entity_select_controller.js b/assets/controllers/elements/structural_entity_select_controller.js index 4b220d5b..5c6f9490 100644 --- a/assets/controllers/elements/structural_entity_select_controller.js +++ b/assets/controllers/elements/structural_entity_select_controller.js @@ -40,10 +40,7 @@ export default class extends Controller { const allowAdd = this.element.getAttribute("data-allow-add") === "true"; const addHint = this.element.getAttribute("data-add-hint") ?? ""; - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } + let settings = { @@ -57,7 +54,7 @@ export default class extends Controller { maxItems: 1, delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$", splitOn: null, - dropdownParent: dropdownParent, + dropdownParent: 'body', searchField: [ {field: "text", weight : 2}, diff --git a/assets/controllers/elements/tagsinput_controller.js b/assets/controllers/elements/tagsinput_controller.js index 14725227..53bf7608 100644 --- a/assets/controllers/elements/tagsinput_controller.js +++ b/assets/controllers/elements/tagsinput_controller.js @@ -33,11 +33,6 @@ export default class extends Controller { _tomSelect; connect() { - let dropdownParent = "body"; - if (this.element.closest('.modal')) { - dropdownParent = null - } - let settings = { plugins: { remove_button:{}, @@ -48,7 +43,7 @@ export default class extends Controller { selectOnTab: true, createOnBlur: true, create: true, - dropdownParent: dropdownParent, + dropdownParent: 'body', }; if(this.element.dataset.autocomplete) { diff --git a/src/Controller/PartListsController.php b/src/Controller/PartListsController.php index 808b0c5d..8ea218f4 100644 --- a/src/Controller/PartListsController.php +++ b/src/Controller/PartListsController.php @@ -36,7 +36,6 @@ use App\Exceptions\InvalidRegexException; use App\Form\Filters\PartFilterType; use App\Services\Parts\PartsTableActionHandler; use App\Services\Trees\NodesListBuilder; -use App\Settings\BehaviorSettings\SidebarSettings; use App\Settings\BehaviorSettings\TableSettings; use Doctrine\DBAL\Exception\DriverException; use Doctrine\ORM\EntityManagerInterface; @@ -57,21 +56,11 @@ class PartListsController extends AbstractController private readonly NodesListBuilder $nodesListBuilder, private readonly DataTableFactory $dataTableFactory, private readonly TranslatorInterface $translator, - private readonly TableSettings $tableSettings, - private readonly SidebarSettings $sidebarSettings, + private readonly TableSettings $tableSettings ) { } - /** - * Gets the filter operator to use by default (INCLUDING_CHILDREN or =) - * @return string - */ - private function getFilterOperator(): string - { - return $this->sidebarSettings->dataStructureNodesTableIncludeChildren ? 'INCLUDING_CHILDREN' : '='; - } - #[Route(path: '/table/action', name: 'table_action', methods: ['POST'])] public function tableAction(Request $request, PartsTableActionHandler $actionHandler): Response { @@ -214,7 +203,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/category_list.html.twig', function (PartFilter $filter) use ($category) { - $filter->category->setOperator($this->getFilterOperator())->setValue($category); + $filter->category->setOperator('INCLUDING_CHILDREN')->setValue($category); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('category')->get('value')); }, [ @@ -232,7 +221,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/footprint_list.html.twig', function (PartFilter $filter) use ($footprint) { - $filter->footprint->setOperator($this->getFilterOperator())->setValue($footprint); + $filter->footprint->setOperator('INCLUDING_CHILDREN')->setValue($footprint); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('footprint')->get('value')); }, [ @@ -250,7 +239,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/manufacturer_list.html.twig', function (PartFilter $filter) use ($manufacturer) { - $filter->manufacturer->setOperator($this->getFilterOperator())->setValue($manufacturer); + $filter->manufacturer->setOperator('INCLUDING_CHILDREN')->setValue($manufacturer); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('manufacturer')->get('value')); }, [ @@ -268,7 +257,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/store_location_list.html.twig', function (PartFilter $filter) use ($storelocation) { - $filter->storelocation->setOperator($this->getFilterOperator())->setValue($storelocation); + $filter->storelocation->setOperator('INCLUDING_CHILDREN')->setValue($storelocation); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('storelocation')->get('value')); }, [ @@ -286,7 +275,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/supplier_list.html.twig', function (PartFilter $filter) use ($supplier) { - $filter->supplier->setOperator($this->getFilterOperator())->setValue($supplier); + $filter->supplier->setOperator('INCLUDING_CHILDREN')->setValue($supplier); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('supplier')->get('value')); }, [ diff --git a/src/DataTables/Filters/Constraints/TextConstraint.php b/src/DataTables/Filters/Constraints/TextConstraint.php index c6a6fe19..31b12a5e 100644 --- a/src/DataTables/Filters/Constraints/TextConstraint.php +++ b/src/DataTables/Filters/Constraints/TextConstraint.php @@ -96,15 +96,14 @@ class TextConstraint extends AbstractConstraint //The CONTAINS, LIKE, STARTS and ENDS operators use the LIKE operator, but we have to build the value string differently $like_value = null; - $escaped_value = str_replace(['%', '_'], ['\%', '\_'], $this->value); if ($this->operator === 'LIKE') { - $like_value = $this->value; //Here we do not escape anything, as the user may provide % and _ wildcards + $like_value = $this->value; } elseif ($this->operator === 'STARTS') { - $like_value = $escaped_value . '%'; + $like_value = $this->value . '%'; } elseif ($this->operator === 'ENDS') { - $like_value = '%' . $escaped_value; + $like_value = '%' . $this->value; } elseif ($this->operator === 'CONTAINS') { - $like_value = '%' . $escaped_value . '%'; + $like_value = '%' . $this->value . '%'; } if ($like_value !== null) { diff --git a/src/DataTables/Filters/PartSearchFilter.php b/src/DataTables/Filters/PartSearchFilter.php index aa8c20f4..60832b26 100644 --- a/src/DataTables/Filters/PartSearchFilter.php +++ b/src/DataTables/Filters/PartSearchFilter.php @@ -144,8 +144,6 @@ class PartSearchFilter implements FilterInterface if ($this->regex) { $queryBuilder->setParameter('search_query', $this->keyword); } else { - //Escape % and _ characters in the keyword - $this->keyword = str_replace(['%', '_'], ['\%', '\_'], $this->keyword); $queryBuilder->setParameter('search_query', '%' . $this->keyword . '%'); } } diff --git a/src/Doctrine/Functions/ILike.php b/src/Doctrine/Functions/ILike.php index ff2d2163..5246220a 100644 --- a/src/Doctrine/Functions/ILike.php +++ b/src/Doctrine/Functions/ILike.php @@ -56,6 +56,7 @@ class ILike extends FunctionNode { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); + // if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) { $operator = 'LIKE'; } elseif ($platform instanceof PostgreSQLPlatform) { @@ -65,12 +66,6 @@ class ILike extends FunctionNode throw new \RuntimeException('Platform ' . gettype($platform) . ' does not support case insensitive like expressions.'); } - $escape = ""; - if ($platform instanceof SQLitePlatform) { - //SQLite needs ESCAPE explicitly defined backslash as escape character - $escape = " ESCAPE '\\'"; - } - - return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . $escape . ')'; + return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . ')'; } -} +} \ No newline at end of file diff --git a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php index a655a0df..d09f1d05 100644 --- a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php +++ b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php @@ -221,7 +221,7 @@ final class DTOtoEntityConverter $attachment = $this->convertFile($image, $image_type); $attachments_grouped[$attachment->getName()][] = $attachment; - if (count($attachments_grouped[$attachment->getName()]) > 1) { + if (count($attachments_grouped[$attachment->getName()] ?? []) > 1) { $attachment->setName($attachment->getName() . ' (' . (count($attachments_grouped[$attachment->getName()]) + 1) . ')'); } @@ -236,7 +236,7 @@ final class DTOtoEntityConverter $attachment = $this->convertFile($datasheet, $datasheet_type); $attachments_grouped[$attachment->getName()][] = $attachment; - if (count($attachments_grouped[$attachment->getName()]) > 1) { + if (count($attachments_grouped[$attachment->getName()] ?? []) > 1) { $attachment->setName($attachment->getName() . ' (' . (count($attachments_grouped[$attachment->getName()])) . ')'); } @@ -357,4 +357,4 @@ final class DTOtoEntityConverter return $tmp; } -} +} \ No newline at end of file diff --git a/src/Services/ProjectSystem/ProjectBuildHelper.php b/src/Services/ProjectSystem/ProjectBuildHelper.php index a541c29d..269c7e4c 100644 --- a/src/Services/ProjectSystem/ProjectBuildHelper.php +++ b/src/Services/ProjectSystem/ProjectBuildHelper.php @@ -31,9 +31,9 @@ use App\Services\Parts\PartLotWithdrawAddHelper; /** * @see \App\Tests\Services\ProjectSystem\ProjectBuildHelperTest */ -final readonly class ProjectBuildHelper +class ProjectBuildHelper { - public function __construct(private PartLotWithdrawAddHelper $withdraw_add_helper) + public function __construct(private readonly PartLotWithdrawAddHelper $withdraw_add_helper) { } @@ -63,35 +63,18 @@ final readonly class ProjectBuildHelper */ public function getMaximumBuildableCount(Project $project): int { - $bom_entries = $project->getBomEntries(); - if ($bom_entries->isEmpty()) { - return 0; - } $maximum_buildable_count = PHP_INT_MAX; - foreach ($bom_entries as $bom_entry) { + foreach ($project->getBomEntries() as $bom_entry) { //Skip BOM entries without a part (as we can not determine that) if (!$bom_entry->isPartBomEntry()) { continue; } + //The maximum buildable count for the whole project is the minimum of all BOM entries $maximum_buildable_count = min($maximum_buildable_count, $this->getMaximumBuildableCountForBOMEntry($bom_entry)); } - return $maximum_buildable_count; - } - /** - * Returns the maximum buildable amount of the given project as string, based on the stock of the used parts in the BOM. - * If the maximum buildable count is infinite, the string '∞' is returned. - * @param Project $project - * @return string - */ - public function getMaximumBuildableCountAsString(Project $project): string - { - $max_count = $this->getMaximumBuildableCount($project); - if ($max_count === PHP_INT_MAX) { - return '∞'; - } - return (string) $max_count; + return $maximum_buildable_count; } /** diff --git a/src/Settings/BehaviorSettings/SidebarSettings.php b/src/Settings/BehaviorSettings/SidebarSettings.php index a1ff6985..1266fa47 100644 --- a/src/Settings/BehaviorSettings/SidebarSettings.php +++ b/src/Settings/BehaviorSettings/SidebarSettings.php @@ -73,11 +73,4 @@ class SidebarSettings */ #[SettingsParameter(label: new TM("settings.behavior.sidebar.rootNodeRedirectsToNewEntity"))] public bool $rootNodeRedirectsToNewEntity = false; - - /** - * @var bool Whether to include child nodes in the data structure nodes table, or only show the selected node's parts. - */ - #[SettingsParameter(label: new TM("settings.behavior.sidebar.data_structure_nodes_table_include_children"), - description: new TM("settings.behavior.sidebar.data_structure_nodes_table_include_children.help"))] - public bool $dataStructureNodesTableIncludeChildren = true; -} +} \ No newline at end of file diff --git a/src/Twig/FormatExtension.php b/src/Twig/FormatExtension.php index 46313aaf..76628ccd 100644 --- a/src/Twig/FormatExtension.php +++ b/src/Twig/FormatExtension.php @@ -82,7 +82,7 @@ final class FormatExtension extends AbstractExtension public function formatBytes(int $bytes, int $precision = 2): string { $size = ['B','kB','MB','GB','TB','PB','EB','ZB','YB']; - $factor = (int) floor((strlen((string) $bytes) - 1) / 3); + $factor = floor((strlen((string) $bytes) - 1) / 3); //We use the real (10 based) SI prefix here return sprintf("%.{$precision}f", $bytes / (1000 ** $factor)) . ' ' . @$size[$factor]; } diff --git a/templates/projects/build/build.html.twig b/templates/projects/build/build.html.twig index a5ff3d9a..783c3794 100644 --- a/templates/projects/build/build.html.twig +++ b/templates/projects/build/build.html.twig @@ -8,8 +8,7 @@ {% endblock %} {% block card_content %} - {% set bom_empty = project.bomEntries | length == 0 %} - {% set can_build = not bom_empty and buildHelper.projectBuildable(project, number_of_builds) %} + {% set can_build = buildHelper.projectBuildable(project, number_of_builds) %} {% import "components/projects.macro.html.twig" as project_macros %} {% if project.status is not empty and project.status != "in_production" %} @@ -18,10 +17,8 @@ {% endif %} -
{% trans %}project.builds.no_stocked_builds{% endtrans %}: {{ project.buildPart.amountSum }}
-{% endif %} +{% endif %} \ No newline at end of file diff --git a/tests/Services/ProjectSystem/ProjectBuildHelperTest.php b/tests/Services/ProjectSystem/ProjectBuildHelperTest.php index 5009f849..3b73cad1 100644 --- a/tests/Services/ProjectSystem/ProjectBuildHelperTest.php +++ b/tests/Services/ProjectSystem/ProjectBuildHelperTest.php @@ -114,22 +114,4 @@ class ProjectBuildHelperTest extends WebTestCase $this->assertSame(0, $this->service->getMaximumBuildableCount($project)); } - - public function testGetMaximumBuildableCountEmpty(): void - { - $project = new Project(); - - $this->assertSame(0, $this->service->getMaximumBuildableCount($project)); - } - - public function testGetMaximumBuildableCountAsString(): void - { - $project = new Project(); - $bom_entry1 = new ProjectBOMEntry(); - $bom_entry1->setName("Test"); - $project->addBomEntry($bom_entry1); - - $this->assertSame('∞', $this->service->getMaximumBuildableCountAsString($project)); - - } } diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index d792c6a5..ad618f86 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -14223,25 +14223,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g