Files
breadcrumb-the-shire/modules/helpdesk/pages/helpdesk/domains/index(default).phtml
fs 0e82e00495 feat(core): endpointUrl() helper — query-safe URLs for module routes
Introduce a helper that returns the registered route TARGET for a given
source path, so URLs built for endpoints that carry query parameters do
not rely on MintyPHP's applyRoutes() rewrite layer — that layer compares
the full request URI (including `?query`) against source paths and
silently misses modules where path ≠ target.

- core/Support/helpers/app.php: endpointUrl($path) consults
  ModuleRegistry::getRoutes(), returns lurl(target) when the path is a
  registered module source path, otherwise falls through to lurl($path).
  Safe fallback when the container or registry is unavailable.
- modules/audit/pages/audit/system-audit/index(default).phtml: replace
  the ad-hoc target-path workaround with endpointUrl('admin/system-audit/
  export'). Callers can now write the natural source path without
  knowing about the rewrite trap.
- Apply the same helper to modules/helpdesk/.../domains/index and
  pages/admin/users/index for consistency — a no-op where path already
  equals target, but establishes the convention: every export/data
  endpoint URL in page configs goes through endpointUrl().
- tests/Support/Helpers/EndpointUrlTest: 5 cases covering source→target
  resolution, idempotence when path == target, fall-through for
  unregistered paths, leading-slash normalization, and graceful
  degradation when the registry is missing. Uses
  AppContainerIsolationTrait per the contract test in
  tests/Architecture/AppContainerIsolationContractTest.

Gates: PHPUnit 1894 OK, PHPStan 0 errors, module:sync ok.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:39:36 +02:00

96 lines
4.2 KiB
PHTML

<?php
$toolbarFilterSchema = is_array($toolbarFilterSchema ?? null) ? $toolbarFilterSchema : [];
$searchToolbarFilterSchema = is_array($searchToolbarFilterSchema ?? null) ? $searchToolbarFilterSchema : [];
$drawerToolbarFilterSchema = is_array($drawerToolbarFilterSchema ?? null) ? $drawerToolbarFilterSchema : [];
$toolbarFilterState = is_array($toolbarFilterState ?? null) ? $toolbarFilterState : [];
$toolbarOptionSets = is_array($toolbarOptionSets ?? null) ? $toolbarOptionSets : [];
$clientFilterSchema = is_array($clientFilterSchema ?? null) ? $clientFilterSchema : [];
$searchConfig = is_array($searchConfig ?? null) ? $searchConfig : null;
$filterChipMeta = is_array($filterChipMeta ?? null) ? $filterChipMeta : [];
$isConfigured = $isConfigured ?? false;
?>
<?php require templatePath('partials/app-flash.phtml'); ?>
<?php if (!$isConfigured): ?>
<div class="notice" data-variant="warning" role="alert">
<p><?php e(t('BC connection is not configured. Please configure the connection in the settings.')); ?></p>
<a href="<?php e(lurl('helpdesk/settings')); ?>" role="button" class="secondary outline small">
<?php e(t('Open settings')); ?>
</a>
</div>
<?php endif; ?>
<?php
ob_start();
require templatePath('partials/app-list-export-dropdown.phtml');
$listTitleActionsHtml = ob_get_clean();
$listTitle = t('Domains');
require templatePath('partials/app-list-titlebar.phtml');
?>
<?php
$filterUiNamespace = 'helpdesk-domains';
require templatePath('partials/app-list-filters.phtml');
?>
<div class="app-list-table">
<div id="helpdesk-domains-grid"></div>
</div>
<dialog
data-app-security-dialog
aria-labelledby="sec-dialog-title"
aria-describedby="sec-dialog-domain"
>
<article class="app-confirm-dialog app-dialog-sm">
<header>
<h2 id="sec-dialog-title"><?php e(t('Security level')); ?></h2>
<button type="button" class="close" data-sec-dialog-close aria-label="<?php e(t('Close')); ?>" title="<?php e(t('Close')); ?>"></button>
</header>
<p id="sec-dialog-domain" class="muted"></p>
<div class="app-form-grid">
<?php $csrfKey = \MintyPHP\Session::$csrfSessionKey; $csrfToken = (string) ($_SESSION[$csrfKey] ?? ''); ?>
<input type="hidden" name="<?php e($csrfKey); ?>" id="sec-dialog-csrf" value="<?php e($csrfToken); ?>">
<div class="app-form-group">
<label for="sec-dialog-level"><?php e(t('Security level')); ?></label>
<select id="sec-dialog-level" name="security_level">
<option value="niedrig"><?php e(t('Low')); ?></option>
<option value="normal"><?php e(t('Normal')); ?></option>
<option value="hoch"><?php e(t('High')); ?></option>
<option value="kritisch"><?php e(t('Critical')); ?></option>
</select>
</div>
<div class="app-form-group">
<label for="sec-dialog-note"><?php e(t('Note')); ?></label>
<textarea id="sec-dialog-note" name="note" rows="3" placeholder="<?php e(t('Optional note...')); ?>"></textarea>
</div>
</div>
<footer>
<button type="button" class="secondary outline" data-sec-dialog-cancel><?php e(t('Cancel')); ?></button>
<button type="button" class="primary" data-sec-dialog-save><?php e(t('Save')); ?></button>
</footer>
</article>
</dialog>
<script src="<?php e(assetVersion('vendor/gridjs/gridjs.umd.js')); ?>"></script>
<script type="application/json" id="page-config-helpdesk-domains"><?php gridJsonForJs([
'dataUrl' => lurl('helpdesk/domains-data'),
'domainBaseUrl' => lurl('helpdesk/domain/'),
'securityLevelDataUrl' => lurl('helpdesk/domains/security-level-data'),
'exportUrl' => endpointUrl('helpdesk/domains/export'),
'gridLang' => gridLang(),
'gridSearch' => $searchConfig,
'filterSchema' => $clientFilterSchema,
'filterChipMeta' => $filterChipMeta,
'labels' => [
'no' => t('No.'),
'customerName' => t('Customer Name'),
'url' => t('URL'),
'state' => t('State'),
'administration' => t('Administration'),
'contractType' => t('Contract type'),
'securityLevel' => t('Security level'),
'note' => t('Note'),
'saveError' => t('Save failed'),
],
]); ?></script>
<script type="module" src="<?php e(assetVersion('modules/helpdesk/js/pages/helpdesk-domains-index.js')); ?>"></script>