diff --git a/docs/modules/market_management/konzepte/market_management_hermes_mcp_api.md b/docs/modules/market_management/konzepte/market_management_hermes_mcp_api.md index 17b00e8..595a594 100644 --- a/docs/modules/market_management/konzepte/market_management_hermes_mcp_api.md +++ b/docs/modules/market_management/konzepte/market_management_hermes_mcp_api.md @@ -138,12 +138,12 @@ Das Ergebnis steht bei `tools/call` als JSON im Textinhalt des ersten - liefert Marktdurchführungen inklusive Markttagen, Kosten und berechneten Verkaufswerten - `market_management.create_market` - erforderlich: `name`, `category_id`, `organizer_id` + - optional: `website_url` als HTTP(S)-URL des Marktes; Eingaben ohne Schema werden als `https://` normalisiert - `market_management.list_market_organizers` - liefert aktive Veranstalterorganisationen - `market_management.create_market_organizer` - erforderlich: `name` - optional: `email`, `phone` - - optional: `website_url` als HTTP(S)-URL des Marktes - `market_management.update_market` - erforderlich: `market_id` - änderbar: `name`, `category_id`, `website_url` diff --git a/modules/market_management/mcp.php b/modules/market_management/mcp.php index 92f85de..1cac574 100644 --- a/modules/market_management/mcp.php +++ b/modules/market_management/mcp.php @@ -75,8 +75,8 @@ function market_management_mcp_tools(): array ['name' => 'market_management.search_markets', 'description' => 'Märkte suchen', 'inputSchema' => ['type' => 'object', 'properties' => $searchProperties]], ['name' => 'market_management.search_occurrences', 'description' => 'Marktdurchführungen suchen und auflisten', 'inputSchema' => ['type' => 'object', 'properties' => $searchProperties]], ['name' => 'market_management.get_market', 'description' => 'Markt mit Marktdurchführungen lesen', 'inputSchema' => ['type' => 'object', 'required' => ['market_id'], 'properties' => ['market_id' => ['type' => 'integer'], 'include_archived' => ['type' => 'boolean']]]], - ['name' => 'market_management.create_market', 'description' => 'Markt mit verpflichtendem Veranstalter, Kategorie und optionaler Website-URL anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['name', 'category_id', 'organizer_id'], 'properties' => ['name' => ['type' => 'string'], 'category_id' => ['type' => 'integer'], 'organizer_id' => ['type' => 'integer'], 'website_url' => ['type' => ['string', 'null'], 'format' => 'uri']]]], - ['name' => 'market_management.update_market', 'description' => 'Markt inklusive Veranstalter und Website-URL ändern', 'inputSchema' => ['type' => 'object', 'required' => ['market_id'], 'properties' => ['market_id' => ['type' => 'integer'], 'name' => ['type' => 'string'], 'category_id' => ['type' => 'integer'], 'organizer_id' => ['type' => 'integer'], 'website_url' => ['type' => ['string', 'null'], 'format' => 'uri']]]], + ['name' => 'market_management.create_market', 'description' => 'Markt mit verpflichtendem Veranstalter, Kategorie und optionaler Website-URL anlegen', 'inputSchema' => ['type' => 'object', 'required' => ['name', 'category_id', 'organizer_id'], 'properties' => ['name' => ['type' => 'string'], 'category_id' => ['type' => 'integer'], 'organizer_id' => ['type' => 'integer'], 'website_url' => ['type' => ['string', 'null']]]]], + ['name' => 'market_management.update_market', 'description' => 'Markt inklusive Veranstalter und Website-URL ändern', 'inputSchema' => ['type' => 'object', 'required' => ['market_id'], 'properties' => ['market_id' => ['type' => 'integer'], 'name' => ['type' => 'string'], 'category_id' => ['type' => 'integer'], 'organizer_id' => ['type' => 'integer'], 'website_url' => ['type' => ['string', 'null']]]]], ['name' => 'market_management.archive_market', 'description' => 'Markt archivieren', 'inputSchema' => ['type' => 'object', 'required' => ['market_id'], 'properties' => ['market_id' => ['type' => 'integer']]]], ['name' => 'market_management.reactivate_market', 'description' => 'Markt reaktivieren', 'inputSchema' => ['type' => 'object', 'required' => ['market_id'], 'properties' => ['market_id' => ['type' => 'integer']]]], ['name' => 'market_management.list_categories', 'description' => 'Bestehende Kategorien lesen', 'inputSchema' => ['type' => 'object', 'properties' => (object) []]], diff --git a/modules/market_management/service.php b/modules/market_management/service.php index f74dabf..c066179 100644 --- a/modules/market_management/service.php +++ b/modules/market_management/service.php @@ -352,6 +352,10 @@ function market_management_optional_url(array $input, string $field): ?string return null; } + if (!preg_match('/^https?:\/\//i', $value)) { + $value = 'https://' . $value; + } + if (filter_var($value, FILTER_VALIDATE_URL) === false || !preg_match('/^https?:\/\//i', $value)) { throw new InvalidArgumentException($field . ' muss eine gültige HTTP(S)-URL sein.'); } diff --git a/modules/shared/auth/ui/home.php b/modules/shared/auth/ui/home.php index f3ae039..a123806 100644 --- a/modules/shared/auth/ui/home.php +++ b/modules/shared/auth/ui/home.php @@ -1322,6 +1322,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $otcM JS; echo " prepareMarketDefinitionDeleteCard();"; echo " const sharedCard = drawer.querySelector('[data-market-definition-card]');"; + echo " const websiteInput = form.querySelector('[data-market-definition-website]'); if (websiteInput) { websiteInput.type = 'text'; }"; echo " if (window.SharedEditableActionCard) { window.SharedEditableActionCard.init(definitionContent); }"; echo " const setDrawerState = (open) => { const drawerOpen = Boolean(open); if (!drawerOpen && marketDefinitionMobileOpen) { marketDefinitionMobileDetail?.restore(); marketDefinitionMobileHost = null; marketDefinitionMobileOpen = false; } const backdropOpen = drawerOpen && !marketDefinitionMobileOpen; drawer.dataset.open = String(drawerOpen); drawer.setAttribute('aria-hidden', String(!drawerOpen)); if (activeMarket?.trigger) { activeMarket.trigger.setAttribute('aria-expanded', String(drawerOpen)); } const backdrop = definitionContent.querySelector('[data-market-definition-drawer-backdrop]'); if (backdrop) { backdrop.dataset.open = String(backdropOpen); backdrop.setAttribute('aria-hidden', String(!backdropOpen)); } document.body.classList.toggle('sg-card-list-page-drawer-open', backdropOpen); };"; echo " const restoreMarketDefinitionMobileDetail = () => { marketDefinitionMobileDetail?.restore(); marketDefinitionMobileHost = null; marketDefinitionMobileOpen = false; };"; diff --git a/scripts/tests/market_management_url_test.php b/scripts/tests/market_management_url_test.php new file mode 100644 index 0000000..5103cce --- /dev/null +++ b/scripts/tests/market_management_url_test.php @@ -0,0 +1,31 @@ + 'www.stadtaffoltern.ch'], 'website_url'); +assert_test($normalized === 'https://www.stadtaffoltern.ch', 'Website without scheme must be normalized to HTTPS'); + +$existingScheme = market_management_optional_url(['website_url' => 'http://www.stadtaffoltern.ch'], 'website_url'); +assert_test($existingScheme === 'http://www.stadtaffoltern.ch', 'Existing HTTP scheme must be preserved'); + +try { + market_management_optional_url(['website_url' => 'not a url'], 'website_url'); + fail_test('Invalid website URL must be rejected'); +} catch (InvalidArgumentException) { +} + +fwrite(STDOUT, "market_management_url_test: OK\n");