Fetches displayName/jobTitle from Graph API and syncs them via SSO profile sync. Also fixes LDAP enabled state in tenant form, adds LDAP save on tenant create, and hardens LDAP port validation precedence. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1050 lines
54 KiB
PHTML
1050 lines
54 KiB
PHTML
<?php
|
|
|
|
/**
|
|
* @var array $values
|
|
* @var string|null $formId
|
|
* @var bool $detailsOpenAll
|
|
* @var bool $isReadOnly
|
|
* @var bool $showDangerZone
|
|
* @var string|null $dangerZoneDeleteFormId
|
|
* @var string|null $dangerZoneWarning
|
|
* @var string|null $dangerZoneActionLabel
|
|
* @var bool $showCustomFieldsTab
|
|
* @var bool $showSsoTab
|
|
* @var string|null $customFieldTenantUuid
|
|
* @var array $customFieldDefinitions
|
|
* @var array $ssoUiState
|
|
*/
|
|
|
|
use MintyPHP\Session;
|
|
|
|
$tenantSsoService = app(\MintyPHP\Service\Auth\TenantSsoService::class);
|
|
$values = $values ?? [];
|
|
$formId = $formId ?? 'tenant-form';
|
|
$detailsOpenAll = $detailsOpenAll ?? false;
|
|
$isReadOnly = $isReadOnly ?? false;
|
|
$showDangerZone = (bool) ($showDangerZone ?? false);
|
|
$dangerZoneDeleteFormId = (string) ($dangerZoneDeleteFormId ?? '');
|
|
$dangerZoneWarning = (string) ($dangerZoneWarning ?? t('This action cannot be undone.'));
|
|
$dangerZoneActionLabel = (string) ($dangerZoneActionLabel ?? t('Delete'));
|
|
$showCustomFieldsTab = (bool) ($showCustomFieldsTab ?? false);
|
|
$showSsoTab = (bool) ($showSsoTab ?? false);
|
|
$customFieldTenantUuid = trim((string) ($customFieldTenantUuid ?? ''));
|
|
$customFieldDefinitions = is_array($customFieldDefinitions ?? null) ? $customFieldDefinitions : [];
|
|
$readonlyAttr = $isReadOnly ? 'readonly' : '';
|
|
$disabledAttr = $isReadOnly ? 'disabled' : '';
|
|
$themes = appThemes();
|
|
$primaryColor = (string) ($values['primary_color'] ?? '');
|
|
$defaultPrimaryColor = appSetting('app_primary_color') ?? '#2fa4a4';
|
|
$useDefaultPrimaryColor = $primaryColor === '';
|
|
$tenantDefaultTheme = strtolower(trim((string) ($values['default_theme'] ?? '')));
|
|
if ($tenantDefaultTheme !== '' && !isset($themes[$tenantDefaultTheme])) {
|
|
$tenantDefaultTheme = '';
|
|
}
|
|
$rawAllowUserThemeMode = $values['allow_user_theme_mode'] ?? ($values['allow_user_theme'] ?? null);
|
|
if ($rawAllowUserThemeMode === null || $rawAllowUserThemeMode === '') {
|
|
$tenantAllowUserThemeMode = '';
|
|
} elseif (in_array(strtolower((string) $rawAllowUserThemeMode), ['1', 'true', 'yes', 'on'], true)) {
|
|
$tenantAllowUserThemeMode = '1';
|
|
} else {
|
|
$tenantAllowUserThemeMode = '0';
|
|
}
|
|
$microsoftEnabledRaw = $values['microsoft_enabled'] ?? false;
|
|
$microsoftEnabled = in_array(strtolower(trim((string) $microsoftEnabledRaw)), ['1', 'true', 'yes', 'on'], true);
|
|
$microsoftEnforceRaw = $values['enforce_microsoft_login'] ?? false;
|
|
$microsoftEnforce = in_array(strtolower(trim((string) $microsoftEnforceRaw)), ['1', 'true', 'yes', 'on'], true);
|
|
$entraTenantId = strtolower(trim((string) ($values['entra_tenant_id'] ?? '')));
|
|
$allowedDomains = trim((string) ($values['allowed_domains'] ?? ''));
|
|
$useSharedAppRaw = $values['use_shared_app'] ?? 1;
|
|
$useSharedApp = !in_array(strtolower(trim((string) $useSharedAppRaw)), ['0', 'false', 'no', 'off'], true);
|
|
$clientIdOverride = trim((string) ($values['client_id_override'] ?? ''));
|
|
$syncProfileOnLoginRaw = $values['sync_profile_on_login'] ?? false;
|
|
$syncProfileOnLogin = in_array(strtolower(trim((string) $syncProfileOnLoginRaw)), ['1', 'true', 'yes', 'on'], true);
|
|
$syncFieldOptions = $tenantSsoService->profileSyncFieldOptions();
|
|
$syncProfileFieldsRaw = $values['sync_profile_fields_list'] ?? ($values['sync_profile_fields'] ?? []);
|
|
$syncProfileFields = $tenantSsoService->normalizeProfileSyncFields($syncProfileFieldsRaw);
|
|
$tenantId = (int) ($values['id'] ?? 0);
|
|
$ssoUiState = is_array($ssoUiState ?? null)
|
|
? $ssoUiState
|
|
: $tenantSsoService->buildMicrosoftUiState($tenantId, $values);
|
|
$ssoConfigComplete = !empty($ssoUiState['config_complete']);
|
|
$ssoConfigErrorLabel = trim((string) ($ssoUiState['config_error_label'] ?? ''));
|
|
$ssoPasswordMode = (string) ($ssoUiState['password_mode'] ?? ($microsoftEnforce ? 'microsoft_only' : ($microsoftEnabled ? 'local_and_microsoft' : 'local_only')));
|
|
$ssoCredentialSource = (string) ($ssoUiState['credential_source'] ?? ($useSharedApp ? 'shared' : 'override'));
|
|
$ssoSyncNeedsGraph = !empty($ssoUiState['sync_needs_graph']);
|
|
$ssoPasswordModeLabel = match ($ssoPasswordMode) {
|
|
'microsoft_only' => t('Microsoft only'),
|
|
'local_and_microsoft' => t('Local password + Microsoft'),
|
|
default => t('Local password only'),
|
|
};
|
|
$ssoCredentialSourceLabel = $ssoCredentialSource === 'shared' ? t('Shared app') : t('Tenant override');
|
|
$ssoStatusTiles = [
|
|
[
|
|
'label' => t('Microsoft login'),
|
|
'value' => $microsoftEnabled ? t('Active') : t('Inactive'),
|
|
'tone' => $microsoftEnabled ? 'success' : 'neutral',
|
|
],
|
|
[
|
|
'label' => t('Configuration complete'),
|
|
'value' => $ssoConfigComplete ? t('Yes') : t('No'),
|
|
'tone' => $ssoConfigComplete ? 'success' : 'warning',
|
|
],
|
|
[
|
|
'label' => t('Password login mode'),
|
|
'value' => $ssoPasswordModeLabel,
|
|
'tone' => match ($ssoPasswordMode) {
|
|
'microsoft_only' => 'warning',
|
|
'local_and_microsoft' => 'info',
|
|
default => 'neutral',
|
|
},
|
|
],
|
|
[
|
|
'label' => t('Credentials source'),
|
|
'value' => $ssoCredentialSourceLabel,
|
|
'tone' => $ssoCredentialSource === 'shared' ? 'info' : 'neutral',
|
|
],
|
|
];
|
|
$allowedDomainsList = preg_split('/[\s,;]+/', strtolower($allowedDomains), -1, PREG_SPLIT_NO_EMPTY);
|
|
$allowedDomainsList = is_array($allowedDomainsList) ? array_values(array_unique($allowedDomainsList)) : [];
|
|
$allowedDomainsCount = count($allowedDomainsList);
|
|
$syncFieldCount = count($syncProfileFields);
|
|
|
|
$ssoSetupBadgeVariant = $microsoftEnabled ? 'success' : 'neutral';
|
|
$ssoSetupBadgeLabel = sprintf(
|
|
'%s: %s',
|
|
t('Microsoft login'),
|
|
$microsoftEnabled ? t('Active') : t('Inactive')
|
|
);
|
|
$ssoShowConfigBadge = $microsoftEnabled;
|
|
$ssoConfigBadgeVariant = $microsoftEnabled ? ($ssoConfigComplete ? 'success' : 'warning') : 'neutral';
|
|
$ssoConfigBadgeLabel = sprintf(
|
|
'%s: %s',
|
|
t('Configuration status'),
|
|
$ssoConfigComplete ? t('Complete') : t('Incomplete')
|
|
);
|
|
|
|
$ssoPolicyBadgeVariant = match ($ssoPasswordMode) {
|
|
'microsoft_only' => 'warning',
|
|
'local_and_microsoft' => 'info',
|
|
default => 'neutral',
|
|
};
|
|
$ssoSyncBadgeVariant = $syncProfileOnLogin ? 'info' : 'neutral';
|
|
$ssoSyncBadgeLabel = $syncProfileOnLogin ? t('Active') : t('Inactive');
|
|
$ssoDomainsBadgeVariant = $allowedDomainsCount > 0 ? 'info' : 'neutral';
|
|
$ssoDomainsBadgeLabel = $allowedDomainsCount > 0
|
|
? sprintf('%d %s', $allowedDomainsCount, t('Allowed email domains'))
|
|
: t('No domain restrictions');
|
|
$ssoCredentialsBadgeVariant = $ssoCredentialSource === 'shared' ? 'info' : 'warning';
|
|
$ssoCredentialsBadgeLabel = $ssoCredentialSourceLabel;
|
|
|
|
$openSetupCard = $detailsOpenAll || !$microsoftEnabled || ($microsoftEnabled && !$ssoConfigComplete);
|
|
$openPolicyCard = $detailsOpenAll || ($microsoftEnabled && $microsoftEnforce);
|
|
$openAccessCard = $detailsOpenAll || ($microsoftEnabled && ($allowedDomainsCount > 0 || $syncProfileOnLogin));
|
|
$openOverrideCard = $detailsOpenAll;
|
|
|
|
?>
|
|
<form id="<?php e($formId); ?>" method="post" data-standard-detail-form="1">
|
|
<div class="app-tabs" data-tabs data-app-component="tabs" data-tabs-param="tab" data-tabs-storage-key="admin-tenant-form"<?php if (!empty($ignoreTabStorage)) { echo ' data-tabs-ignore-storage'; } ?>>
|
|
<div class="app-tabs-nav">
|
|
<button type="button" data-tab="basic" data-tab-default><?php e(t('Master data')); ?></button>
|
|
<?php if ($showCustomFieldsTab): ?>
|
|
<button type="button" data-tab="custom_fields"><?php e(t('User custom fields')); ?></button>
|
|
<?php endif; ?>
|
|
<?php if ($showSsoTab): ?>
|
|
<button type="button" data-tab="sso"><?php e(t('Microsoft SSO')); ?></button>
|
|
<button type="button" data-tab="ldap"><?php e(t('LDAP')); ?></button>
|
|
<?php endif; ?>
|
|
<button type="button" data-tab="visibility"><?php e(t('Visibility')); ?></button>
|
|
<?php if ($showDangerZone && $dangerZoneDeleteFormId !== ''): ?>
|
|
<button type="button" data-tab="danger"><?php e(t('Danger zone')); ?></button>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div data-tab-panel="basic">
|
|
<div class="grid">
|
|
<label for="description">
|
|
<span><?php e(t('Description')); ?></span>
|
|
<input required type="text" name="description" id="description" value="<?php e($values['description'] ?? ''); ?>"
|
|
<?php e($readonlyAttr); ?> />
|
|
</label>
|
|
</div>
|
|
<label for="address">
|
|
<span><?php e(t('Address')); ?></span>
|
|
<input type="text" name="address" id="address" value="<?php e($values['address'] ?? ''); ?>" <?php e($readonlyAttr); ?> />
|
|
</label>
|
|
<div class="grid">
|
|
<label for="postal_code">
|
|
<span><?php e(t('Postal code')); ?></span>
|
|
<input type="text" name="postal_code" id="postal_code" value="<?php e($values['postal_code'] ?? ''); ?>" <?php e($readonlyAttr); ?> />
|
|
</label>
|
|
<label for="city">
|
|
<span><?php e(t('City')); ?></span>
|
|
<input type="text" name="city" id="city" value="<?php e($values['city'] ?? ''); ?>" <?php e($readonlyAttr); ?> />
|
|
</label>
|
|
</div>
|
|
<div class="grid">
|
|
<label for="country">
|
|
<span><?php e(t('Country')); ?></span>
|
|
<input type="text" name="country" id="country" value="<?php e($values['country'] ?? ''); ?>" <?php e($readonlyAttr); ?> />
|
|
</label>
|
|
<label for="region">
|
|
<span><?php e(t('Region')); ?></span>
|
|
<input type="text" name="region" id="region" value="<?php e($values['region'] ?? ''); ?>" <?php e($readonlyAttr); ?> />
|
|
</label>
|
|
</div>
|
|
<div class="grid">
|
|
<label for="vat_id">
|
|
<span><?php e(t('VAT ID')); ?></span>
|
|
<input type="text" name="vat_id" id="vat_id" value="<?php e($values['vat_id'] ?? ''); ?>" <?php e($readonlyAttr); ?> />
|
|
</label>
|
|
<label for="tax_number">
|
|
<span><?php e(t('Tax number')); ?></span>
|
|
<input type="text" name="tax_number" id="tax_number" value="<?php e($values['tax_number'] ?? ''); ?>" <?php e($readonlyAttr); ?> />
|
|
</label>
|
|
</div>
|
|
<hr>
|
|
<details name="tenant-master-contact" <?php e($detailsOpenAll ? 'open' : ''); ?>>
|
|
<summary><?php e(t('Contact')); ?></summary>
|
|
<hr>
|
|
<div class="grid">
|
|
<label for="phone">
|
|
<span><?php e(t('Phone')); ?></span>
|
|
<input type="tel" name="phone" id="phone" value="<?php e($values['phone'] ?? ''); ?>" <?php e($readonlyAttr); ?> />
|
|
</label>
|
|
<label for="fax">
|
|
<span><?php e(t('Fax')); ?></span>
|
|
<input type="tel" name="fax" id="fax" value="<?php e($values['fax'] ?? ''); ?>" <?php e($readonlyAttr); ?> />
|
|
</label>
|
|
</div>
|
|
<div class="grid">
|
|
<label for="email">
|
|
<span><?php e(t('Email')); ?></span>
|
|
<input type="email" name="email" id="email" value="<?php e($values['email'] ?? ''); ?>" <?php e($readonlyAttr); ?> />
|
|
</label>
|
|
<label for="website">
|
|
<span><?php e(t('Website')); ?></span>
|
|
<input type="url" name="website" id="website" value="<?php e($values['website'] ?? ''); ?>" <?php e($readonlyAttr); ?> />
|
|
</label>
|
|
</div>
|
|
<div class="grid">
|
|
<label for="support_email">
|
|
<span><?php e(t('Support email')); ?></span>
|
|
<input type="email" name="support_email" id="support_email" value="<?php e($values['support_email'] ?? ''); ?>"
|
|
<?php e($readonlyAttr); ?> />
|
|
</label>
|
|
<label for="support_phone">
|
|
<span><?php e(t('Support phone')); ?></span>
|
|
<input type="tel" name="support_phone" id="support_phone" value="<?php e($values['support_phone'] ?? ''); ?>"
|
|
<?php e($readonlyAttr); ?> />
|
|
</label>
|
|
</div>
|
|
<label for="billing_email">
|
|
<span><?php e(t('Billing email')); ?></span>
|
|
<input type="email" name="billing_email" id="billing_email" value="<?php e($values['billing_email'] ?? ''); ?>"
|
|
<?php e($readonlyAttr); ?> />
|
|
</label>
|
|
</details>
|
|
<hr>
|
|
<details name="tenant-master-legal" <?php e($detailsOpenAll ? 'open' : ''); ?>>
|
|
<summary><?php e(t('Legal')); ?></summary>
|
|
<hr>
|
|
<div class="grid">
|
|
<label for="privacy_url">
|
|
<span><?php e(t('Privacy URL')); ?></span>
|
|
<input type="url" name="privacy_url" id="privacy_url" value="<?php e($values['privacy_url'] ?? ''); ?>"
|
|
<?php e($readonlyAttr); ?> />
|
|
</label>
|
|
<label for="imprint_url">
|
|
<span><?php e(t('Imprint URL')); ?></span>
|
|
<input type="url" name="imprint_url" id="imprint_url" value="<?php e($values['imprint_url'] ?? ''); ?>"
|
|
<?php e($readonlyAttr); ?> />
|
|
</label>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
|
|
<?php if ($showCustomFieldsTab): ?>
|
|
<div data-tab-panel="custom_fields">
|
|
<?php if ($customFieldTenantUuid === ''): ?>
|
|
<?php
|
|
$emptyState = [
|
|
'message' => t('Save tenant first to manage custom fields'),
|
|
'size' => 'compact',
|
|
];
|
|
if (!$isReadOnly) {
|
|
$emptyState['action'] = [
|
|
'label' => t('Create'),
|
|
'form' => $formId,
|
|
];
|
|
}
|
|
require templatePath('partials/app-empty-state.phtml');
|
|
?>
|
|
<?php else: ?>
|
|
<blockquote data-variant="info">
|
|
<?php e(t('Define custom fields here. They appear per user in the "Custom fields" tab and can be maintained there.')); ?>
|
|
</blockquote>
|
|
<details class="app-details-card" name="tenant-custom-fields-create">
|
|
<summary>
|
|
<span class="app-details-card-summary-title">
|
|
<?php e(t('Add custom field')); ?>
|
|
</span>
|
|
</summary>
|
|
<div class="app-details-card-container">
|
|
<div class="grid">
|
|
<label for="tenant-custom-field-label">
|
|
<span><?php e(t('Label')); ?></span>
|
|
<input
|
|
id="tenant-custom-field-label"
|
|
form="tenant-custom-field-create-form"
|
|
required
|
|
type="text"
|
|
name="label"
|
|
<?php e($disabledAttr); ?>>
|
|
<small class="muted"><?php e(t('The label is shown in user custom fields and in address book filters.')); ?></small>
|
|
</label>
|
|
|
|
<label for="tenant-custom-field-type">
|
|
<span><?php e(t('Field type')); ?></span>
|
|
<select
|
|
id="tenant-custom-field-type"
|
|
form="tenant-custom-field-create-form"
|
|
name="type"
|
|
data-custom-field-type-source
|
|
data-custom-field-group="tenant-custom-field-create"
|
|
<?php e($disabledAttr); ?>>
|
|
<option value="text"><?php e(t('Text')); ?></option>
|
|
<option value="textarea"><?php e(t('Textarea')); ?></option>
|
|
<option value="select"><?php e(t('Select')); ?></option>
|
|
<option value="multiselect"><?php e(t('Multiselect')); ?></option>
|
|
<option value="boolean"><?php e(t('Boolean')); ?></option>
|
|
<option value="date"><?php e(t('Date')); ?></option>
|
|
</select>
|
|
<small class="muted"><?php e(t('The field type controls input and filter behavior.')); ?></small>
|
|
</label>
|
|
</div>
|
|
<div class="grid">
|
|
<fieldset data-custom-field-filterable-target data-custom-field-group="tenant-custom-field-create">
|
|
<label class="app-field">
|
|
<input form="tenant-custom-field-create-form" role="switch" type="checkbox" name="is_filterable" value="1"
|
|
data-custom-field-filterable-input
|
|
<?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Filterable')); ?></span>
|
|
</label>
|
|
<small class="muted"><?php e(t('Filterable fields are available in address book filters.')); ?></small>
|
|
</fieldset>
|
|
<fieldset>
|
|
<label class="app-field">
|
|
<input form="tenant-custom-field-create-form" role="switch" type="checkbox" name="active" value="1" checked
|
|
<?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Active')); ?></span>
|
|
</label>
|
|
<small class="muted"><?php e(t('Inactive fields are hidden in forms and filters.')); ?></small>
|
|
</fieldset>
|
|
</div>
|
|
<div data-custom-field-options-target data-custom-field-group="tenant-custom-field-create">
|
|
<label for="tenant-custom-field-options-text">
|
|
<span><?php e(t('Options (one per line)')); ?></span>
|
|
<textarea
|
|
id="tenant-custom-field-options-text"
|
|
form="tenant-custom-field-create-form"
|
|
name="options_text"
|
|
rows="5"
|
|
placeholder="<?php e(t('Example: manager|Manager')); ?>"
|
|
<?php e($readonlyAttr); ?>></textarea>
|
|
<small class="muted"><?php e(t('One option per line. Optional format: key|Label.')); ?></small>
|
|
</label>
|
|
</div>
|
|
<button type="submit" class="app-action-success" form="tenant-custom-field-create-form" <?php e($disabledAttr); ?>>
|
|
<?php e(t('Add custom field')); ?>
|
|
</button>
|
|
</div>
|
|
</details>
|
|
<?php if (!$customFieldDefinitions): ?>
|
|
<?php
|
|
$emptyState = [
|
|
'message' => t('No custom fields configured yet'),
|
|
'size' => 'compact',
|
|
];
|
|
require templatePath('partials/app-empty-state.phtml');
|
|
?>
|
|
<?php endif; ?>
|
|
<?php foreach ($customFieldDefinitions as $definition): ?>
|
|
<?php
|
|
$definitionId = (int) ($definition['id'] ?? 0);
|
|
$definitionUuid = (string) ($definition['uuid'] ?? '');
|
|
if ($definitionId <= 0 || $definitionUuid === '') {
|
|
continue;
|
|
}
|
|
$updateFormId = 'tenant-custom-field-update-form-' . $definitionId;
|
|
$deleteFormId = 'tenant-custom-field-delete-form-' . $definitionId;
|
|
$typeGroupId = 'tenant-custom-field-edit-' . $definitionId;
|
|
$definitionType = strtolower((string) ($definition['type'] ?? 'text'));
|
|
$definitionTypeLabel = t(ucfirst($definitionType));
|
|
$definitionOptions = is_array($definition['options'] ?? null) ? $definition['options'] : [];
|
|
$definitionOptionsText = '';
|
|
foreach ($definitionOptions as $option) {
|
|
$optionKey = trim((string) ($option['option_key'] ?? ''));
|
|
$optionLabel = trim((string) ($option['label'] ?? ''));
|
|
if ($optionLabel === '') {
|
|
continue;
|
|
}
|
|
$definitionOptionsText .= ($definitionOptionsText === '' ? '' : "\n")
|
|
. ($optionKey !== '' ? ($optionKey . '|') : '')
|
|
. $optionLabel;
|
|
}
|
|
?>
|
|
<details class="app-details-card" name="tenant-custom-fields">
|
|
<summary>
|
|
<span class="app-details-card-summary-title">
|
|
<?php e((string) ($definition['label'] ?? '')); ?>
|
|
</span>
|
|
<span class="app-details-card-summary-meta">
|
|
<span class="badge" data-variant="neutral"><?php e($definitionTypeLabel); ?></span>
|
|
</span>
|
|
</summary>
|
|
<div class="app-details-card-container">
|
|
|
|
<div class="grid">
|
|
<label>
|
|
<span><?php e(t('Label')); ?></span>
|
|
<input
|
|
type="text"
|
|
form="<?php e($updateFormId); ?>"
|
|
name="label"
|
|
required
|
|
value="<?php e((string) ($definition['label'] ?? '')); ?>"
|
|
<?php e($readonlyAttr); ?>>
|
|
<small class="muted"><?php e(t('The label is shown in user custom fields and in address book filters.')); ?></small>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Field type')); ?></span>
|
|
<select
|
|
form="<?php e($updateFormId); ?>"
|
|
name="type"
|
|
data-custom-field-type-source
|
|
data-custom-field-group="<?php e($typeGroupId); ?>"
|
|
<?php e($disabledAttr); ?>>
|
|
<?php foreach (['text', 'textarea', 'select', 'multiselect', 'boolean', 'date'] as $typeOption): ?>
|
|
<option value="<?php e($typeOption); ?>" <?php e($definitionType === $typeOption ? 'selected' : ''); ?>>
|
|
<?php e(t(ucfirst($typeOption))); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<small class="muted"><?php e(t('The field type controls input and filter behavior.')); ?></small>
|
|
</label>
|
|
</div>
|
|
<div class="grid">
|
|
<fieldset data-custom-field-filterable-target data-custom-field-group="<?php e($typeGroupId); ?>">
|
|
<label class="app-field">
|
|
<input type="checkbox" role="switch" form="<?php e($updateFormId); ?>" name="is_filterable" value="1"
|
|
data-custom-field-filterable-input
|
|
<?php e(!empty($definition['is_filterable']) ? 'checked' : ''); ?>
|
|
<?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Filterable')); ?></span>
|
|
</label>
|
|
<small class="muted"><?php e(t('Filterable fields are available in address book filters.')); ?></small>
|
|
</fieldset>
|
|
<fieldset>
|
|
<label class="app-field">
|
|
<input type="hidden" form="<?php e($updateFormId); ?>" name="active" value="0">
|
|
<input type="checkbox" role="switch" form="<?php e($updateFormId); ?>" name="active" value="1"
|
|
<?php e(!empty($definition['active']) ? 'checked' : ''); ?>
|
|
<?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Active')); ?></span>
|
|
</label>
|
|
<small class="muted"><?php e(t('Inactive fields are hidden in forms and filters.')); ?></small>
|
|
</fieldset>
|
|
</div>
|
|
<div data-custom-field-options-target data-custom-field-group="<?php e($typeGroupId); ?>">
|
|
<label>
|
|
<span><?php e(t('Options (one per line)')); ?></span>
|
|
<textarea
|
|
form="<?php e($updateFormId); ?>"
|
|
name="options_text"
|
|
rows="5"
|
|
placeholder="<?php e(t('Example: manager|Manager')); ?>"
|
|
<?php e($readonlyAttr); ?>><?php e($definitionOptionsText); ?></textarea>
|
|
</label>
|
|
<small class="muted"><?php e(t('One option per line. Optional format: key|Label.')); ?></small>
|
|
</div>
|
|
<hr>
|
|
<div class="grid">
|
|
<button type="submit" class="app-action-success" form="<?php e($updateFormId); ?>" <?php e($disabledAttr); ?>>
|
|
<?php e(t('Save')); ?>
|
|
</button>
|
|
<button type="submit" class="danger" form="<?php e($deleteFormId); ?>" <?php e($disabledAttr); ?>>
|
|
<?php e(t('Delete custom field')); ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($showSsoTab): ?>
|
|
<div data-tab-panel="sso" data-tenant-sso-root>
|
|
<small class="app-form-info-tiles-title"><?php e(t('SSO configuration status')); ?></small>
|
|
<div class="app-form-info-tiles">
|
|
<?php foreach ($ssoStatusTiles as $ssoStatusTile): ?>
|
|
<?php
|
|
$infoTileLabel = (string) ($ssoStatusTile['label'] ?? '');
|
|
$infoTileValue = (string) ($ssoStatusTile['value'] ?? '');
|
|
$infoTileTone = (string) ($ssoStatusTile['tone'] ?? 'neutral');
|
|
require templatePath('partials/app-form-info-tile.phtml');
|
|
?>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php if ($microsoftEnabled && !$ssoConfigComplete): ?>
|
|
<blockquote data-variant="warning">
|
|
<strong><?php e(t('SSO setup is incomplete')); ?></strong><br>
|
|
<?php if ($ssoConfigErrorLabel !== ''): ?>
|
|
<?php e($ssoConfigErrorLabel); ?>
|
|
<?php else: ?>
|
|
<?php e(t('Complete the required Microsoft configuration before enforcing Microsoft-only login.')); ?>
|
|
<?php endif; ?>
|
|
</blockquote>
|
|
<?php endif; ?>
|
|
|
|
<details class="app-details-card" name="tenant-sso-setup" <?php e($openSetupCard ? 'open' : ''); ?>>
|
|
<summary>
|
|
<span class="app-details-card-summary-title"><?php e(t('Required Microsoft configuration')); ?></span>
|
|
<span class="app-details-card-summary-meta">
|
|
<span class="badge" data-variant="<?php e($ssoSetupBadgeVariant); ?>"><?php e($ssoSetupBadgeLabel); ?></span>
|
|
<?php if ($ssoShowConfigBadge): ?>
|
|
<span class="badge" data-variant="<?php e($ssoConfigBadgeVariant); ?>"><?php e($ssoConfigBadgeLabel); ?></span>
|
|
<?php endif; ?>
|
|
</span>
|
|
</summary>
|
|
<div class="app-details-card-container">
|
|
<label class="app-field">
|
|
<input
|
|
type="checkbox"
|
|
role="switch"
|
|
name="microsoft_enabled"
|
|
value="1"
|
|
data-tenant-sso-enabled
|
|
<?php e($microsoftEnabled ? 'checked' : ''); ?>
|
|
<?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Enable Microsoft login for this tenant')); ?></span>
|
|
</label>
|
|
<small class="muted"><?php e(t('Required for Microsoft login.')); ?></small>
|
|
<div data-tenant-sso-when-enabled>
|
|
<hr>
|
|
<label>
|
|
<span><?php e(t('Entra tenant ID (tid)')); ?></span>
|
|
<input
|
|
type="text"
|
|
name="entra_tenant_id"
|
|
value="<?php e($entraTenantId); ?>"
|
|
placeholder="00000000-0000-0000-0000-000000000000"
|
|
<?php e($readonlyAttr); ?>>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="app-details-card" name="tenant-sso-policy" data-tenant-sso-when-enabled <?php e($openPolicyCard ? 'open' : ''); ?>>
|
|
<summary>
|
|
<span class="app-details-card-summary-title"><?php e(t('Login policy')); ?></span>
|
|
<span class="app-details-card-summary-meta">
|
|
<span class="badge" data-variant="<?php e($ssoPolicyBadgeVariant); ?>"><?php e($ssoPasswordModeLabel); ?></span>
|
|
</span>
|
|
</summary>
|
|
<div class="app-details-card-container">
|
|
<label class="app-field">
|
|
<input
|
|
type="checkbox"
|
|
role="switch"
|
|
name="enforce_microsoft_login"
|
|
value="1"
|
|
<?php e($microsoftEnforce ? 'checked' : ''); ?>
|
|
<?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Allow Microsoft-only login (disable local password login)')); ?></span>
|
|
</label>
|
|
<small class="muted"><?php e(t('Microsoft-only login requires a complete configuration')); ?></small>
|
|
<hr>
|
|
<label class="app-field">
|
|
<span><?php e(t('Remember login after Microsoft sign-in')); ?></span>
|
|
<select name="microsoft_auto_remember_mode" <?php e($readonlyAttr); ?>>
|
|
<option value="inherit" <?php if (($form['auto_remember_mode'] ?? null) === null) echo 'selected'; ?>><?php e(t('Inherit global default')); ?></option>
|
|
<option value="1" <?php if (($form['auto_remember_mode'] ?? null) === 1 || ($form['auto_remember_mode'] ?? null) === '1') echo 'selected'; ?>><?php e(t('Force on')); ?></option>
|
|
<option value="0" <?php if (($form['auto_remember_mode'] ?? null) === 0 || ($form['auto_remember_mode'] ?? null) === '0') echo 'selected'; ?>><?php e(t('Force off')); ?></option>
|
|
</select>
|
|
<small class="muted"><?php e(t('Controls whether Microsoft logins automatically persist a remember-me token.')); ?></small>
|
|
</label>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="app-details-card" name="tenant-sso-access-sync" data-tenant-sso-when-enabled <?php e($openAccessCard ? 'open' : ''); ?>>
|
|
<summary>
|
|
<span class="app-details-card-summary-title"><?php e(t('Access and profile sync')); ?></span>
|
|
<span class="app-details-card-summary-meta">
|
|
<span class="badge" data-variant="<?php e($ssoDomainsBadgeVariant); ?>"><?php e($ssoDomainsBadgeLabel); ?></span>
|
|
<span class="badge" data-variant="<?php e($ssoSyncBadgeVariant); ?>">
|
|
<?php e(t('Profile sync')); ?>: <?php e($ssoSyncBadgeLabel); ?>
|
|
</span>
|
|
<?php if ($syncProfileOnLogin): ?>
|
|
<span class="badge" data-variant="info">
|
|
<?php e(t('Sync fields')); ?>: <?php e((string) $syncFieldCount); ?>
|
|
</span>
|
|
<?php endif; ?>
|
|
</span>
|
|
</summary>
|
|
<div class="app-details-card-container">
|
|
<label>
|
|
<span><?php e(t('Allowed email domains')); ?></span>
|
|
<textarea name="allowed_domains" rows="3" placeholder="example.com partner.com" <?php e($readonlyAttr); ?>><?php e($allowedDomains); ?></textarea>
|
|
<small><?php e(t('Optional allow-list, comma or newline separated (example.com).')); ?></small>
|
|
</label>
|
|
<hr>
|
|
<label class="app-field">
|
|
<input
|
|
type="checkbox"
|
|
role="switch"
|
|
name="sync_profile_on_login"
|
|
value="1"
|
|
data-tenant-sso-sync-toggle
|
|
<?php e($syncProfileOnLogin ? 'checked' : ''); ?>
|
|
<?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Profile sync on Microsoft login')); ?></span>
|
|
</label>
|
|
<small class="muted"><?php e(t('User profile fields are global and affect all tenants of this user')); ?></small>
|
|
<div data-tenant-sso-sync-fields>
|
|
<hr>
|
|
<label><span><?php e(t('Sync fields')); ?></span></label>
|
|
<div class="grid grid-1-1">
|
|
<?php foreach ($syncFieldOptions as $syncFieldKey => $syncFieldLabel): ?>
|
|
<label class="app-field">
|
|
<input
|
|
type="checkbox"
|
|
name="sync_profile_fields[]"
|
|
value="<?php e($syncFieldKey); ?>"
|
|
<?php e(in_array($syncFieldKey, $syncProfileFields, true) ? 'checked' : ''); ?>
|
|
<?php e($disabledAttr); ?>>
|
|
<span><?php e(t($syncFieldLabel)); ?></span>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php if ($ssoSyncNeedsGraph): ?>
|
|
<hr>
|
|
<small class="muted"><?php e(t('Phone/mobile/avatar sync requires Microsoft Graph (User.Read)')); ?></small>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<details
|
|
class="app-details-card"
|
|
name="tenant-sso-advanced"
|
|
data-tenant-sso-override-details
|
|
data-tenant-sso-when-enabled
|
|
data-remember-state="admin-tenant-sso-override"
|
|
<?php e($openOverrideCard ? 'open' : ''); ?>>
|
|
<summary>
|
|
<span class="app-details-card-summary-title"><?php e(t('App credentials (advanced)')); ?></span>
|
|
<span class="app-details-card-summary-meta">
|
|
<span class="badge" data-variant="<?php e($ssoCredentialsBadgeVariant); ?>"><?php e($ssoCredentialsBadgeLabel); ?></span>
|
|
</span>
|
|
</summary>
|
|
<div class="app-details-card-container">
|
|
<label class="app-field">
|
|
<input type="hidden" name="use_shared_app" value="0">
|
|
<input
|
|
type="checkbox"
|
|
role="switch"
|
|
name="use_shared_app"
|
|
value="1"
|
|
data-tenant-sso-shared-toggle
|
|
<?php e($useSharedApp ? 'checked' : ''); ?>
|
|
<?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Use shared app credentials from settings')); ?></span>
|
|
</label>
|
|
<small class="muted"><?php e(t('Override only if this tenant needs a separate Entra app.')); ?></small>
|
|
<div class="grid" data-tenant-sso-override-fields>
|
|
<label>
|
|
<span><?php e(t('Client ID override')); ?></span>
|
|
<input type="text" name="client_id_override" value="<?php e($clientIdOverride); ?>" <?php e($readonlyAttr); ?>>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Client secret override')); ?></span>
|
|
<input type="password" name="client_secret_override" value="" autocomplete="new-password" <?php e($readonlyAttr); ?>>
|
|
<small><?php e(t('Write-only. Leave empty to keep current secret.')); ?></small>
|
|
</label>
|
|
</div>
|
|
<label class="app-field" data-tenant-sso-override-fields>
|
|
<input type="checkbox" name="clear_client_secret_override" value="1" <?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Clear override secret')); ?></span>
|
|
</label>
|
|
</div>
|
|
</details>
|
|
|
|
</div>
|
|
|
|
<div data-tab-panel="ldap">
|
|
<?php
|
|
// ── LDAP configuration section ────────────────────────────────
|
|
$ldapUiState = is_array($ldapUiState ?? null) ? $ldapUiState : [];
|
|
if (isset($values['ldap_enabled'])) {
|
|
$ldapEnabled = in_array(strtolower(trim((string) $values['ldap_enabled'])), ['1', 'true', 'yes', 'on'], true);
|
|
} elseif (isset($ldapConfig['enabled'])) {
|
|
$ldapEnabled = !empty($ldapConfig['enabled']);
|
|
} else {
|
|
$ldapEnabled = false;
|
|
}
|
|
$ldapEnforce = !empty($values['enforce_ldap_login'] ?? $ldapConfig['enforce_ldap_login'] ?? false);
|
|
$ldapHost = trim((string) ($values['ldap_host'] ?? $values['host'] ?? $ldapConfig['host'] ?? ''));
|
|
$ldapPort = (int) ($values['ldap_port'] ?? $values['port'] ?? $ldapConfig['port'] ?? 389);
|
|
$ldapEncryptionMode = (string) ($values['ldap_encryption_mode'] ?? $values['encryption_mode'] ?? $ldapConfig['encryption_mode'] ?? 'starttls');
|
|
$ldapVerifyTls = !array_key_exists('verify_tls_certificate', ($ldapConfig ?? [])) || !empty($values['ldap_verify_tls_certificate'] ?? $ldapConfig['verify_tls_certificate'] ?? true);
|
|
$ldapBaseDn = trim((string) ($values['ldap_base_dn'] ?? $values['base_dn'] ?? $ldapConfig['base_dn'] ?? ''));
|
|
$ldapUserSearchFilter = trim((string) ($values['ldap_user_search_filter'] ?? $values['user_search_filter'] ?? $ldapConfig['user_search_filter'] ?? '(&(objectClass=user)(sAMAccountName=%s))'));
|
|
$ldapUserSearchScope = (string) ($values['ldap_user_search_scope'] ?? $values['user_search_scope'] ?? $ldapConfig['user_search_scope'] ?? 'sub');
|
|
$ldapBindMethod = (string) ($values['ldap_bind_method'] ?? $values['bind_method'] ?? $ldapConfig['bind_method'] ?? 'search_then_bind');
|
|
$ldapBindUsernameFormat = trim((string) ($values['ldap_bind_username_format'] ?? $values['bind_username_format'] ?? $ldapConfig['bind_username_format'] ?? ''));
|
|
$ldapUniqueIdAttribute = trim((string) ($values['ldap_unique_id_attribute'] ?? $values['unique_id_attribute'] ?? $ldapConfig['unique_id_attribute'] ?? 'objectGUID'));
|
|
$ldapAttributeMap = $ldapConfig['attribute_map'] ?? [];
|
|
if (is_string($ldapAttributeMap)) {
|
|
$ldapAttributeMap = json_decode($ldapAttributeMap, true) ?: [];
|
|
}
|
|
$ldapSyncProfileOnLogin = !empty($values['ldap_sync_profile_on_login'] ?? $ldapConfig['sync_profile_on_login'] ?? false);
|
|
$ldapSyncFieldOptions = $tenantSsoService->ldapProfileSyncFieldOptions();
|
|
$ldapSyncProfileFields = $tenantSsoService->normalizeLdapProfileSyncFields($values['ldap_sync_profile_fields'] ?? $ldapConfig['sync_profile_fields_list'] ?? []);
|
|
$ldapAllowedDomains = trim((string) ($values['ldap_allowed_domains'] ?? $ldapConfig['allowed_domains'] ?? ''));
|
|
$ldapNetworkTimeout = (int) ($values['ldap_network_timeout'] ?? $ldapConfig['network_timeout'] ?? 5);
|
|
$ldapConfigComplete = !empty($ldapUiState['config_complete']);
|
|
$ldapConfigErrorLabel = trim((string) ($ldapUiState['config_error_label'] ?? ''));
|
|
$openLdapSetupCard = $detailsOpenAll || !$ldapEnabled || ($ldapEnabled && !$ldapConfigComplete);
|
|
?>
|
|
<div data-tenant-ldap-root>
|
|
|
|
<div class="app-form-info-tiles">
|
|
<?php
|
|
$infoTileLabel = t('LDAP login');
|
|
$infoTileValue = $ldapEnabled ? t('Active') : t('Inactive');
|
|
$infoTileTone = $ldapEnabled ? 'success' : 'neutral';
|
|
require templatePath('partials/app-form-info-tile.phtml');
|
|
$infoTileLabel = t('Configuration complete');
|
|
$infoTileValue = $ldapConfigComplete ? t('Yes') : t('No');
|
|
$infoTileTone = $ldapConfigComplete ? 'success' : 'warning';
|
|
require templatePath('partials/app-form-info-tile.phtml');
|
|
?>
|
|
</div>
|
|
|
|
<?php if ($ldapEnabled && !$ldapConfigComplete && $ldapConfigErrorLabel !== ''): ?>
|
|
<blockquote data-variant="warning">
|
|
<strong><?php e(t('LDAP setup is incomplete')); ?></strong><br>
|
|
<?php e($ldapConfigErrorLabel); ?>
|
|
</blockquote>
|
|
<?php endif; ?>
|
|
|
|
<details class="app-details-card" name="tenant-ldap-setup" <?php e($openLdapSetupCard ? 'open' : ''); ?>>
|
|
<summary>
|
|
<span class="app-details-card-summary-title"><?php e(t('LDAP connection settings')); ?></span>
|
|
<span class="app-details-card-summary-meta">
|
|
<span class="badge" data-variant="<?php e($ldapEnabled ? 'success' : 'neutral'); ?>">
|
|
<?php e(t('LDAP login')); ?>: <?php e($ldapEnabled ? t('Active') : t('Inactive')); ?>
|
|
</span>
|
|
</span>
|
|
</summary>
|
|
<div class="app-details-card-container">
|
|
<label class="app-field">
|
|
<input type="checkbox" role="switch" name="ldap_enabled" value="1"
|
|
data-tenant-ldap-enabled
|
|
<?php e($ldapEnabled ? 'checked' : ''); ?> <?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Enable LDAP login for this tenant')); ?></span>
|
|
</label>
|
|
<div data-tenant-ldap-when-enabled>
|
|
<hr>
|
|
<div class="grid grid-1-1">
|
|
<label>
|
|
<span><?php e(t('LDAP Host')); ?></span>
|
|
<input type="text" name="ldap_host" value="<?php e($ldapHost); ?>"
|
|
placeholder="ldap.example.com" <?php e($readonlyAttr); ?>>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Port')); ?></span>
|
|
<input type="number" name="ldap_port" value="<?php e((string) $ldapPort); ?>"
|
|
min="1" max="65535" <?php e($readonlyAttr); ?>>
|
|
</label>
|
|
</div>
|
|
<div class="grid grid-1-1">
|
|
<label>
|
|
<span><?php e(t('Encryption')); ?></span>
|
|
<select name="ldap_encryption_mode" <?php e($disabledAttr); ?>>
|
|
<option value="starttls" <?php e($ldapEncryptionMode === 'starttls' ? 'selected' : ''); ?>>STARTTLS</option>
|
|
<option value="ldaps" <?php e($ldapEncryptionMode === 'ldaps' ? 'selected' : ''); ?>>LDAPS (SSL)</option>
|
|
<option value="none" <?php e($ldapEncryptionMode === 'none' ? 'selected' : ''); ?>><?php e(t('None (not recommended)')); ?></option>
|
|
</select>
|
|
</label>
|
|
<label class="app-field">
|
|
<input type="checkbox" name="ldap_verify_tls_certificate" value="1"
|
|
<?php e($ldapVerifyTls ? 'checked' : ''); ?> <?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Verify TLS certificate')); ?></span>
|
|
</label>
|
|
</div>
|
|
<label>
|
|
<span><?php e(t('Base DN')); ?></span>
|
|
<input type="text" name="ldap_base_dn" value="<?php e($ldapBaseDn); ?>"
|
|
placeholder="DC=corp,DC=example,DC=com" <?php e($readonlyAttr); ?>>
|
|
</label>
|
|
<hr>
|
|
<label>
|
|
<span><?php e(t('Bind DN (service account)')); ?></span>
|
|
<input type="text" name="ldap_bind_dn" value="" autocomplete="off"
|
|
placeholder="CN=svc-ldap,OU=Service Accounts,DC=corp,DC=example,DC=com" <?php e($readonlyAttr); ?>>
|
|
<small class="muted"><?php e(t('Write-only. Leave empty to keep current value.')); ?></small>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Bind password')); ?></span>
|
|
<input type="password" name="ldap_bind_password" value="" autocomplete="new-password" <?php e($readonlyAttr); ?>>
|
|
<small class="muted"><?php e(t('Write-only. Leave empty to keep current value.')); ?></small>
|
|
</label>
|
|
<label class="app-field">
|
|
<input type="checkbox" name="clear_ldap_bind_password" value="1" <?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Clear bind password')); ?></span>
|
|
</label>
|
|
<hr>
|
|
<p>
|
|
<button type="button" class="secondary outline small" id="ldap-test-connection-button"
|
|
data-ldap-test-url="<?php e(lurl('admin/tenants/ldap-test-connection')); ?>"
|
|
data-tenant-id="<?php e((string) $tenantId); ?>">
|
|
<i class="bi bi-plug" aria-hidden="true"></i> <?php e(t('Test connection')); ?>
|
|
</button>
|
|
<span id="ldap-test-connection-result" aria-live="polite"></span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="app-details-card" name="tenant-ldap-search" data-tenant-ldap-when-enabled <?php e($detailsOpenAll ? 'open' : ''); ?>>
|
|
<summary>
|
|
<span class="app-details-card-summary-title"><?php e(t('Search and bind settings')); ?></span>
|
|
</summary>
|
|
<div class="app-details-card-container">
|
|
<label>
|
|
<span><?php e(t('Bind method')); ?></span>
|
|
<select name="ldap_bind_method" <?php e($disabledAttr); ?>>
|
|
<option value="search_then_bind" <?php e($ldapBindMethod === 'search_then_bind' ? 'selected' : ''); ?>><?php e(t('Search then bind (recommended)')); ?></option>
|
|
<option value="direct_bind" <?php e($ldapBindMethod === 'direct_bind' ? 'selected' : ''); ?>><?php e(t('Direct bind')); ?></option>
|
|
</select>
|
|
<small class="muted"><?php e(t('Search-then-bind uses a service account to find the user DN first, then binds with the user credentials.')); ?></small>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('User search filter')); ?></span>
|
|
<input type="text" name="ldap_user_search_filter" value="<?php e($ldapUserSearchFilter); ?>"
|
|
placeholder="(&(objectClass=user)(sAMAccountName=%s))" <?php e($readonlyAttr); ?>>
|
|
<small class="muted"><?php e(t('Use %s as placeholder for the username. Examples: (&(objectClass=user)(sAMAccountName=%s)) for AD, (&(objectClass=inetOrgPerson)(uid=%s)) for OpenLDAP')); ?></small>
|
|
</label>
|
|
<div class="grid grid-1-1">
|
|
<label>
|
|
<span><?php e(t('Search scope')); ?></span>
|
|
<select name="ldap_user_search_scope" <?php e($disabledAttr); ?>>
|
|
<option value="sub" <?php e($ldapUserSearchScope === 'sub' ? 'selected' : ''); ?>><?php e(t('Subtree (recursive)')); ?></option>
|
|
<option value="one" <?php e($ldapUserSearchScope === 'one' ? 'selected' : ''); ?>><?php e(t('One level')); ?></option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Network timeout (seconds)')); ?></span>
|
|
<input type="number" name="ldap_network_timeout" value="<?php e((string) $ldapNetworkTimeout); ?>"
|
|
min="1" max="30" <?php e($readonlyAttr); ?>>
|
|
</label>
|
|
</div>
|
|
<label>
|
|
<span><?php e(t('Direct bind username format')); ?></span>
|
|
<input type="text" name="ldap_bind_username_format" value="<?php e($ldapBindUsernameFormat); ?>"
|
|
placeholder="%s@corp.example.com" <?php e($readonlyAttr); ?>>
|
|
<small class="muted"><?php e(t('Only for direct bind. Use %s as placeholder. Example: %s@corp.example.com or uid=%s,ou=People,dc=example,dc=com')); ?></small>
|
|
</label>
|
|
<label>
|
|
<span><?php e(t('Unique ID attribute')); ?></span>
|
|
<input type="text" name="ldap_unique_id_attribute" value="<?php e($ldapUniqueIdAttribute); ?>"
|
|
placeholder="objectGUID" <?php e($readonlyAttr); ?>>
|
|
<small class="muted"><?php e(t('AD: objectGUID, OpenLDAP: entryUUID, FreeIPA: ipaUniqueID')); ?></small>
|
|
</label>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="app-details-card" name="tenant-ldap-mapping" data-tenant-ldap-when-enabled <?php e($detailsOpenAll ? 'open' : ''); ?>>
|
|
<summary>
|
|
<span class="app-details-card-summary-title"><?php e(t('Attribute mapping and sync')); ?></span>
|
|
</summary>
|
|
<div class="app-details-card-container">
|
|
<small class="muted"><?php e(t('Map application fields to LDAP directory attributes.')); ?></small>
|
|
<div class="grid grid-1-1">
|
|
<?php
|
|
$defaultMap = [
|
|
'first_name' => 'givenName',
|
|
'last_name' => 'sn',
|
|
'email' => 'mail',
|
|
'phone' => 'telephoneNumber',
|
|
'mobile' => 'mobile',
|
|
];
|
|
foreach ($defaultMap as $appField => $defaultLdapAttr):
|
|
$currentLdapAttr = $ldapAttributeMap[$appField] ?? $defaultLdapAttr;
|
|
?>
|
|
<label>
|
|
<span><?php e(t(ucfirst(str_replace('_', ' ', $appField)))); ?> →</span>
|
|
<input type="text" name="ldap_attribute_map[<?php e($appField); ?>]"
|
|
value="<?php e($currentLdapAttr); ?>" <?php e($readonlyAttr); ?>>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<hr>
|
|
<label class="app-field">
|
|
<input type="checkbox" role="switch" name="ldap_sync_profile_on_login" value="1"
|
|
data-tenant-ldap-sync-toggle
|
|
<?php e($ldapSyncProfileOnLogin ? 'checked' : ''); ?> <?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Profile sync on LDAP login')); ?></span>
|
|
</label>
|
|
<small class="muted"><?php e(t('User profile fields are global and affect all tenants of this user')); ?></small>
|
|
<div data-tenant-ldap-sync-fields class="grid grid-1-1">
|
|
<?php foreach ($ldapSyncFieldOptions as $syncFieldKey => $syncFieldLabel): ?>
|
|
<label class="app-field">
|
|
<input type="checkbox" name="ldap_sync_profile_fields[]"
|
|
value="<?php e($syncFieldKey); ?>"
|
|
<?php e(in_array($syncFieldKey, $ldapSyncProfileFields, true) ? 'checked' : ''); ?>
|
|
<?php e($disabledAttr); ?>>
|
|
<span><?php e(t($syncFieldLabel)); ?></span>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<hr>
|
|
<label class="app-field">
|
|
<input type="checkbox" role="switch" name="enforce_ldap_login" value="1"
|
|
<?php e($ldapEnforce ? 'checked' : ''); ?> <?php e($disabledAttr); ?>>
|
|
<span><?php e(t('Allow LDAP-only login (disable local password login)')); ?></span>
|
|
</label>
|
|
<small class="muted"><?php e(t('LDAP-only login requires a complete configuration')); ?></small>
|
|
<hr>
|
|
<label>
|
|
<span><?php e(t('Allowed email domains')); ?></span>
|
|
<textarea name="ldap_allowed_domains" rows="2" placeholder="example.com" <?php e($readonlyAttr); ?>><?php e($ldapAllowedDomains); ?></textarea>
|
|
<small><?php e(t('Optional allow-list, comma or newline separated.')); ?></small>
|
|
</label>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div data-tab-panel="visibility">
|
|
<blockquote data-variant="info">
|
|
<?php e(t('Using the default color applies the global system appearance.')); ?>
|
|
</blockquote>
|
|
<div class="grid">
|
|
<fieldset>
|
|
<legend><small><?php e(t('Primary color')); ?></small></legend>
|
|
<label for="primary_color">
|
|
<input type="color" name="primary_color" id="primary_color"
|
|
value="<?php e($primaryColor !== '' ? $primaryColor : $defaultPrimaryColor); ?>"
|
|
<?php e($readonlyAttr); ?> />
|
|
<div>
|
|
<?php e(t('Choose color')); ?>
|
|
</div>
|
|
</label>
|
|
<small class="muted"><?php e(t('The primary color affects buttons and accent elements across the app.')); ?></small>
|
|
</fieldset>
|
|
<fieldset>
|
|
<legend>
|
|
<small>
|
|
<?php e(t('Use default color')); ?>
|
|
</small>
|
|
</legend>
|
|
<label class="app-field">
|
|
<input type="hidden" name="primary_color_use_default" value="0">
|
|
<input type="checkbox" role="switch" name="primary_color_use_default" value="1"
|
|
data-color-default-toggle
|
|
data-color-target="#primary_color"
|
|
data-color-default="<?php e($defaultPrimaryColor); ?>"
|
|
<?php e($useDefaultPrimaryColor ? 'checked' : ''); ?>
|
|
<?php e($readonlyAttr); ?>>
|
|
<span><?php e(t('Use default color')); ?></span>
|
|
</label>
|
|
<small class="muted"><?php e(t('Be careful - this resets the tenants color')); ?></small>
|
|
</fieldset>
|
|
</div>
|
|
<div class="grid">
|
|
<fieldset>
|
|
<legend><small><?php e(t('Default theme')); ?></small></legend>
|
|
<select name="default_theme" <?php e($disabledAttr); ?>>
|
|
<option value=""><?php e(t('Inherit global setting')); ?></option>
|
|
<?php foreach ($themes as $themeKey => $themeLabel): ?>
|
|
<option value="<?php e($themeKey); ?>" <?php e($tenantDefaultTheme === $themeKey ? 'selected' : ''); ?>>
|
|
<?php e(t($themeLabel)); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<small class="muted"><?php e(t('If set, this tenant uses its own default theme instead of the global setting.')); ?></small>
|
|
</fieldset>
|
|
<fieldset>
|
|
<legend><small><?php e(t('User theme policy')); ?></small></legend>
|
|
<select name="allow_user_theme_mode" <?php e($disabledAttr); ?>>
|
|
<option value="" <?php e($tenantAllowUserThemeMode === '' ? 'selected' : ''); ?>>
|
|
<?php e(t('Inherit global setting')); ?>
|
|
</option>
|
|
<option value="1" <?php e($tenantAllowUserThemeMode === '1' ? 'selected' : ''); ?>>
|
|
<?php e(t('Force allow user theme')); ?>
|
|
</option>
|
|
<option value="0" <?php e($tenantAllowUserThemeMode === '0' ? 'selected' : ''); ?>>
|
|
<?php e(t('Force disallow user theme')); ?>
|
|
</option>
|
|
</select>
|
|
<small class="muted"><?php e(t('Controls whether users in this tenant can choose their own theme.')); ?></small>
|
|
</fieldset>
|
|
</div>
|
|
<?php
|
|
$statusFieldName = 'status';
|
|
$statusFieldId = 'tenant-status';
|
|
$statusFieldValue = (string) ($values['status'] ?? 'active');
|
|
$statusFieldOptions = [
|
|
'active' => t('Active'),
|
|
'inactive' => t('Inactive'),
|
|
];
|
|
$statusFieldHint = t('Inactive tenants are excluded from user access and tenant-scoped data.');
|
|
require templatePath('partials/app-visibility-status-field.phtml');
|
|
?>
|
|
</div>
|
|
<?php if ($showDangerZone && $dangerZoneDeleteFormId !== ''): ?>
|
|
<div data-tab-panel="danger">
|
|
<?php
|
|
$dangerFormId = $dangerZoneDeleteFormId;
|
|
$dangerLegend = t('Delete');
|
|
$dangerWarning = $dangerZoneWarning;
|
|
$dangerButtonLabel = $dangerZoneActionLabel;
|
|
require templatePath('partials/app-danger-zone-delete-field.phtml');
|
|
?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php Session::getCsrfInput(); ?>
|
|
</form>
|
|
<?php if ($showCustomFieldsTab && $customFieldTenantUuid !== ''): ?>
|
|
<form id="tenant-custom-field-create-form" method="post"
|
|
action="admin/tenants/custom-field-create/<?php e($customFieldTenantUuid); ?>" hidden>
|
|
<?php Session::getCsrfInput(); ?>
|
|
</form>
|
|
<?php foreach ($customFieldDefinitions as $definition): ?>
|
|
<?php
|
|
$definitionId = (int) ($definition['id'] ?? 0);
|
|
$definitionUuid = (string) ($definition['uuid'] ?? '');
|
|
if ($definitionId <= 0 || $definitionUuid === '') {
|
|
continue;
|
|
}
|
|
$updateFormId = 'tenant-custom-field-update-form-' . $definitionId;
|
|
$deleteFormId = 'tenant-custom-field-delete-form-' . $definitionId;
|
|
?>
|
|
<form id="<?php e($updateFormId); ?>" method="post"
|
|
action="admin/tenants/custom-field-update/<?php e($definitionUuid); ?>" hidden>
|
|
<?php Session::getCsrfInput(); ?>
|
|
</form>
|
|
<form id="<?php e($deleteFormId); ?>" method="post"
|
|
action="admin/tenants/custom-field-delete/<?php e($definitionUuid); ?>" hidden
|
|
data-detail-confirm-message="<?php e(t('Delete custom field')); ?>?"
|
|
data-detail-action-kind="delete">
|
|
<?php Session::getCsrfInput(); ?>
|
|
</form>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|