Skip to content
Snippets Groups Projects
Unverified Commit 36ebb535 authored by Pavel Břoušek's avatar Pavel Břoušek Committed by GitHub
Browse files

Merge pull request #25 from CESNET/idphint

feat: multivalue idphint
parents a6f1c0be 8b1bcdd7
No related branches found
No related tags found
No related merge requests found
# simplesamlphp-module-campusmultiauth
Thanks to this module, you can use a saml:SP authentication source together with another authentication source providing basic auth (discovery service and login form are displayed on a single page). This module also supports [aarc_idp_hint](https://zenodo.org/record/4596667/files/AARC-G061-A_specification_for_IdP_hinting.pdf), so you can even skip the login page and be redirected to the targeted identity provider.
Thanks to this module, you can use a saml:SP authentication source together with another authentication source providing basic auth (discovery service and login form are displayed on a single page).
## Theme configuration
......@@ -83,6 +83,8 @@ This component represents a form with username and password. It can be used only
`password_placeholder` - this is displayed as a placeholder in the input for the password. If you want to add localization, you can write the value as a map with language codes as keys and localized strings as values. If current language is not found in keys, the **_first one_** is used instead. If not set at all, it displays a default value.
`entityid` - entityid of the identity provider. Needed for idp hinting.
`priority` - can be set to `primary`, default value is `secondary`. It should be primary if you want users to use this component if they are able to.
`end_col` - on a desktop, components are divided to two columns. If you want this component to be the last one in the first column, set this option to `true`.
......@@ -131,6 +133,18 @@ Each identity is a map with the following possible options:
`background_color` - background around the logo. Defined as a [CSS color value](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value).
## Hinting
To help the user choose the right institution to log in, this module supports following standards:
### [aarc_idp_hint](https://zenodo.org/record/4596667/files/AARC-G061-A_specification_for_IdP_hinting.pdf)
A service provider can choose which identity provider should user use, he/she then skips the login page and is redirected to the targeted identity provider.
### [idphint](https://aarc-project.eu/wp-content/uploads/2019/04/AARC-G049-A_specification_for_IdP_hinting-v6.pdf)
A service provider can choose which identity provider(s) should user use. If there is only one option, the user is redirected directly to the identity provider. Otherwise, user chooses from identity providers sent in idphint parameter.
## Deployment
The easiest way is to use [docker-campusidp](https://github.com/cesnet/docker-campusidp), which includes this module together with SimpleSAMLphp and PHP-FPM.
......
......@@ -28,6 +28,7 @@ $config = [
'cs' => 'Heslo',
'en' => 'Password',
],
'entityid' => 'https://idp2.ics.muni.cz/idp/shibboleth',
],
[
'name' => 'searchbox',
......
......@@ -15,6 +15,7 @@ use SimpleSAML\Module;
use SimpleSAML\Module\core\Auth\UserPassBase;
use SimpleSAML\Session;
use SimpleSAML\Utils;
use Transliterator;
class Campusidp extends Source
{
......@@ -38,6 +39,8 @@ class Campusidp extends Source
public const COOKIE_PASSWORD = 'password';
public const IDP_HINT_BUTTONS_LIMIT = 5;
private $sources;
private $userPassSourceName;
......@@ -231,6 +234,157 @@ class Campusidp extends Source
return false;
}
public static function findSearchboxesToDisplay($hintedIdps, $config)
{
$result = [];
for ($i = 0; $i < count($config['components']); $i++) {
if ($config['components'][$i]['name'] === 'searchbox') {
$ch = curl_init();
curl_setopt(
$ch,
CURLOPT_URL,
Module::getModuleURL(
'campusmultiauth/idpSearch.php?idphint=' . json_encode(
$hintedIdps
) . '&skipMatching=true' . '&index=' . $i
)
);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$idps = json_decode(curl_exec($ch));
curl_close($ch);
if (!empty($idps->items)) {
$result[] = $i;
}
}
}
return $result;
}
public static function findIndividualIdentitiesToDisplay($hintedIdps, $config)
{
$result = [];
for ($i = 0; $i < count($config['components']); $i++) {
if ($config['components'][$i]['name'] === 'individual_identities') {
$componentToDisplay = false;
foreach ($config['components'][$i]['identities'] as $identity) {
if (in_array($identity['upstream_idp'], $hintedIdps, true)) {
$componentToDisplay = true;
break;
}
}
if ($componentToDisplay) {
$result[] = $i;
}
}
}
return $result;
}
public static function getOrPositions($searchboxesToDisplay, $individualIdentitiesToDisplay, $idphint, $config)
{
$result = [];
$componentsToDisplay = [];
$endColComponent = -1;
for ($i = 0; $i < count($config['components']); $i++) {
if ($config['components'][$i]['name'] === 'local_login' && in_array(
$config['components'][$i]['entityid'],
$idphint,
true
)) {
$componentsToDisplay[] = $i;
}
if (!empty($config['components'][$i]['end_col']) && $config['components'][$i]['end_col'] === true) {
$endColComponent = $i;
}
}
$componentsToDisplay = array_merge($componentsToDisplay, $searchboxesToDisplay, $individualIdentitiesToDisplay);
foreach ($componentsToDisplay as $index1) {
if ($index1 <= $endColComponent) {
foreach ($componentsToDisplay as $index2) {
if ($index1 < $index2 && $index2 <= $endColComponent) {
$result[] = $index1;
break;
}
}
} else {
foreach ($componentsToDisplay as $index2) {
if ($index1 < $index2) {
$result[] = $index1;
break;
}
}
}
}
return $result;
}
public static function getIdpsMatchedBySearchTerm($metadata, $searchTerm)
{
$filteredMetadata = [];
$transliterator = Transliterator::createFromRules(
':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: Lower(); :: NFC;',
Transliterator::FORWARD
);
foreach ($metadata as $entityid => $idpentry) {
if (!empty($idpentry['name']) && is_array($idpentry['name'])) {
foreach ($idpentry['name'] as $key => $value) {
if (str_contains(
$transliterator->transliterate($value),
$transliterator->transliterate($searchTerm)
)) {
$filteredMetadata[$entityid] = $idpentry;
break;
}
}
}
if (!in_array($idpentry, $filteredMetadata, true) && !empty($idpentry['description']) && is_array(
$idpentry['description']
)) {
foreach ($idpentry['description'] as $key => $value) {
if (str_contains(
$transliterator->transliterate($value),
$transliterator->transliterate($searchTerm)
)) {
$filteredMetadata[$entityid] = $idpentry;
break;
}
}
}
if (!in_array($idpentry, $filteredMetadata, true) && !empty($idpentry['url']) && is_array(
$idpentry['url']
)) {
foreach ($idpentry['url'] as $key => $value) {
if (str_contains(strtolower($value), strtolower($searchTerm))) {
$filteredMetadata[$entityid] = $idpentry;
break;
}
}
}
}
return $filteredMetadata;
}
public function logout(&$state)
{
assert(is_array($state));
......
......@@ -16,18 +16,22 @@
{% endif %}
</h4>
{% set index = 0 %}
{% for idp in configuration.identities %}
<div class="{% if muni_jvs %}margin-bottom-12{% endif %}{% if loop.index0 >= configuration.number_shown %} idp-hidden d-none vhide{% endif %}">
<button class="btn-individual-identity btn {% if muni_jvs %}btn-primary btn-border color-{{ configuration.priority }} hover-none-{{ configuration.priority }}{% else %}btn-light shadow-sm {% if configuration.priority == 'primary' %}border-dark text-dark{% else %}border-muted text-muted{% endif %} border-2{% endif %}" type="submit" name="idpentityid" value="{{ idp.upstream_idp }}">
{% if muni_jvs %}<span class="no-uppercase color-{{ configuration.priority }} individual-identity-span-wrap">{% endif %}
<img class="individual-identity-logo{% if not muni_jvs %} border-end border-2 border-{% if configuration.priority == 'primary' %}dark{% else %}muted{% endif %}{% endif %}" {% if idp.background_color is defined %}style="background-color: {{ idp.background_color }}"{% endif %} src="{{ idp.logo }}" alt=""/>
<span class="idp-text">{{ '{campusmultiauth:sign_in_with}'|trans }}{{ " " }}{% if attribute(idp.name, currentLanguage) is defined %}{{ attribute(idp.name, currentLanguage) }}
{% elseif idp.name is defined and idp.name is iterable and idp.name is not empty %}{{ idp.name | first }}
{% else %}{{ idp.name }}
{% endif %}</span>
{% if muni_jvs %}</span>{% endif %}
</button>
</div>
{% if idphint is not defined or idp.upstream_idp in idphint %}
<div class="{% if muni_jvs %}margin-bottom-12{% endif %}{% if index >= configuration.number_shown %} idp-hidden d-none vhide{% endif %}">
<button class="btn-individual-identity btn {% if muni_jvs %}btn-primary btn-border color-{{ configuration.priority }} hover-none-{{ configuration.priority }}{% else %}btn-light shadow-sm {% if configuration.priority == 'primary' %}border-dark text-dark{% else %}border-muted text-muted{% endif %} border-2{% endif %}" type="submit" name="idpentityid" value="{{ idp.upstream_idp }}">
{% if muni_jvs %}<span class="no-uppercase color-{{ configuration.priority }} individual-identity-span-wrap">{% endif %}
<img class="individual-identity-logo img-searchbox{% if not muni_jvs %} border-end border-2 border-{% if configuration.priority == 'primary' %}dark{% else %}muted{% endif %}{% endif %}" {% if idp.background_color is defined %}style="background-color: {{ idp.background_color }}"{% endif %} src="{{ idp.logo }}" alt=""/>
<span class="idp-text">{{ '{campusmultiauth:sign_in_with}'|trans }}{{ " " }}{% if attribute(idp.name, currentLanguage) is defined %}{{ attribute(idp.name, currentLanguage) }}
{% elseif idp.name is defined and idp.name is iterable and idp.name is not empty %}{{ idp.name | first }}
{% else %}{{ idp.name }}
{% endif %}</span>
{% if muni_jvs %}</span>{% endif %}
</button>
</div>
{% set index = index + 1 %}
{% endif %}
{% endfor %}
<input type="hidden" name="authstate" value="{{ authstate }}" />
......
......@@ -18,6 +18,9 @@
{{ parent() }}
<script src="/{{baseurlpath}}module.php/campusmultiauth/resources/jquery-3.6.0.min.js"></script>
<script src="/{{baseurlpath}}module.php/campusmultiauth/resources/selectize/js/standalone/selectize.min.js"></script>
<meta name="idphint" value="{% if idphint is defined %}{{ idphint | json_encode | raw }}{% endif %}">
<script src="/{{baseurlpath}}module.php/campusmultiauth/resources/campus-idp.js"></script>
{% endblock %}
......@@ -31,32 +34,34 @@
{% block content %}
<div class="{% if muni_jvs %}grid{% else %}row{% endif %}">
<div class="{% if muni_jvs %}grid__cell size--m--2-4 first-col{% else %}col-md-6{% endif %} wrap-col">
{% if hint_component_config is defined and hint_component_config.name == 'individual_identities' %}
{% include '@campusmultiauth/includes/individual-identities.twig' with {'configuration': hint_component_config, 'component_index': 0} %}
{% else %}
{% for component_configuration in wayf_config.components %}
{% if component_configuration.name == 'local_login' and (idphint is not defined or component_configuration.entityid in idphint) %}
{% include '@campusmultiauth/includes/local-login.twig' with {'configuration': component_configuration} %}
{% elseif component_configuration.name == 'individual_identities' and (individual_identities_to_display is not defined or loop.index0 in individual_identities_to_display) %}
{% include '@campusmultiauth/includes/individual-identities.twig' with {'configuration': component_configuration, 'component_index': loop.index0} %}
{% elseif component_configuration.name == 'searchbox' and (searchboxes_to_display is not defined or loop.index0 in searchboxes_to_display) %}
{% include '@campusmultiauth/includes/searchbox.twig' with {'configuration': component_configuration, 'component_index': loop.index0} %}
{% endif %}
{% for component_configuration in wayf_config.components %}
{% if component_configuration.name == 'local_login' %}
{% include '@campusmultiauth/includes/local-login.twig' with {'configuration': component_configuration} %}
{% elseif component_configuration.name == 'individual_identities' %}
{% include '@campusmultiauth/includes/individual-identities.twig' with {'configuration': component_configuration, 'component_index': loop.index0} %}
{% elseif component_configuration.name == 'searchbox' %}
{% include '@campusmultiauth/includes/searchbox.twig' with {'configuration': component_configuration, 'component_index': loop.index0} %}
{% endif %}
{% if component_configuration.end_col is defined and component_configuration.end_col %}
</div>
<div class="{% if muni_jvs %}grid__cell size--m--2-4{% else %}col-md-6{% endif %} wrap-col">
{% endif %}
{% if component_configuration.end_col is defined and component_configuration.end_col %}
</div>
<div class="{% if muni_jvs %}grid__cell size--m--2-4{% else %}col-md-6{% endif %} wrap-col">
{% endif %}
{% if not loop.last and (component_configuration.end_col is not defined or component_configuration.end_col != true) %}
<div class="hrline color-secondary">
<span{% if not muni_jvs %} class="bg-light"{% endif %}>{{ '{campusmultiauth:or}'|trans }}</span>
</div>
{% elseif not loop.last and (component_configuration.end_col is defined and component_configuration.end_col == true) %}
<div class="hrline last-col-component color-secondary">
<span{% if not muni_jvs %} class="bg-light"{% endif %}>{{ '{campusmultiauth:or}'|trans }}</span>
</div>
{% if not loop.last and (component_configuration.end_col is not defined or component_configuration.end_col != true) and (or_positions is not defined or loop.index0 in or_positions) %}
<div class="hrline color-secondary">
<span{% if not muni_jvs %} class="bg-light"{% endif %}>{{ '{campusmultiauth:or}'|trans }}</span>
</div>
{% elseif not loop.last and (component_configuration.end_col is defined and component_configuration.end_col == true) %}
<div class="hrline last-col-component color-secondary">
<span{% if not muni_jvs %} class="bg-light"{% endif %}>{{ '{campusmultiauth:or}'|trans }}</span>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</div>
</div>
{% endblock %}
......
......@@ -8,118 +8,103 @@ use SimpleSAML\Module\campusmultiauth\Auth\Source\Campusidp;
header('Content-type: application/json');
$index = $_GET['index'];
$config = Configuration::getConfig('module_campusmultiauth.php')->toArray();
$searchBox = $config['components'][$index];
$language = $_GET['language'] ?? 'en';
$metadataStorageHandler = MetaDataStorageHandler::getMetadataHandler();
$metadata = $metadataStorageHandler->getList();
$searchTerm = $_GET['q'];
$transliterator = Transliterator::createFromRules(
':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: Lower(); :: NFC;',
Transliterator::FORWARD
);
if (!empty($_GET['idphint']) && !isset($_GET['index'])) {
$filteredData = array_intersect_key($metadata, array_flip(json_decode($_GET['idphint'])));
} else {
$index = $_GET['index'];
$searchTerm = $_GET['q'] ?? '';
$skipMatching = $_GET['skipMatching'] ?? false;
if (
!empty($searchBox['include']['upstream_idps']) ||
!empty($searchBox['include']['tags']) ||
!empty($searchBox['include']['registration_authorities'])
) {
$filteredMetadata = [];
foreach ($metadata as $entityid => $idpentry) {
if (!empty($searchBox['include']['tags'])) {
foreach ($searchBox['include']['tags'] as $tag) {
if ($tag === $idpentry['tag']) {
$filteredMetadata[$entityid] = $idpentry;
break;
}
}
$config = Configuration::getConfig('module_campusmultiauth.php')->toArray();
$searchBox = $config['components'][$index];
if (!empty($_GET['idphint'])) {
$idphint = $_GET['idphint'];
if (!is_array($idphint)) {
$idphint = json_decode($idphint);
}
if (!empty($searchBox['include']['registration_authorities']) && empty($filteredMetadata[$entityid])) {
foreach ($searchBox['include']['registration_authorities'] as $registrationAuthority) {
if (!empty($idpentry['RegistrationInfo']['registrationAuthority']) && $idpentry['RegistrationInfo']['registrationAuthority'] === $registrationAuthority) {
$filteredMetadata[$entityid] = $idpentry;
break;
$metadata = array_intersect_key($metadata, array_flip($idphint));
}
if (
!empty($searchBox['include']['upstream_idps']) ||
!empty($searchBox['include']['tags']) ||
!empty($searchBox['include']['registration_authorities'])
) {
$filteredMetadata = [];
foreach ($metadata as $entityid => $idpentry) {
if (!empty($searchBox['include']['tags'])) {
foreach ($searchBox['include']['tags'] as $tag) {
if ($tag === $idpentry['tag']) {
$filteredMetadata[$entityid] = $idpentry;
break;
}
}
}
}
if (!empty($searchBox['include']['upstream_idps']) && empty($filteredMetadata[$entityid])) {
foreach ($searchBox['include']['upstream_idps'] as $upstreamIdp) {
if ($upstreamIdp === $entityid) {
$filteredMetadata[$entityid] = $idpentry;
break;
if (!empty($searchBox['include']['registration_authorities']) && empty($filteredMetadata[$entityid])) {
foreach ($searchBox['include']['registration_authorities'] as $registrationAuthority) {
if (!empty($idpentry['RegistrationInfo']['registrationAuthority']) && $idpentry['RegistrationInfo']['registrationAuthority'] === $registrationAuthority) {
$filteredMetadata[$entityid] = $idpentry;
break;
}
}
}
}
}
$metadata = $filteredMetadata;
}
foreach ($metadata as $entityid => $idpentry) {
if (!empty($searchBox['exclude']['tags'])) {
foreach ($searchBox['exclude']['tags'] as $tag) {
if ($tag === $idpentry['tag']) {
unset($metadata[$entityid]);
break;
if (!empty($searchBox['include']['upstream_idps']) && empty($filteredMetadata[$entityid])) {
foreach ($searchBox['include']['upstream_idps'] as $upstreamIdp) {
if ($upstreamIdp === $entityid) {
$filteredMetadata[$entityid] = $idpentry;
break;
}
}
}
}
}
if (!empty($searchBox['exclude']['registration_authorities']) && !empty($metadata[$entityid])) {
foreach ($searchBox['exclude']['registration_authorities'] as $registrationAuthority) {
if ($registrationAuthority === $idpentry['RegistrationInfo']['registrationAuthority']) {
unset($metadata[$entityid]);
break;
}
}
$metadata = $filteredMetadata;
}
if (!empty($searchBox['exclude']['upstream_idps']) && !empty($metadata[$entityid])) {
foreach ($searchBox['exclude']['upstream_idps'] as $upstreamIdp) {
if ($upstreamIdp === $entityid) {
unset($metadata[$entityid]);
break;
foreach ($metadata as $entityid => $idpentry) {
if (!empty($searchBox['exclude']['tags'])) {
foreach ($searchBox['exclude']['tags'] as $tag) {
if ($tag === $idpentry['tag']) {
unset($metadata[$entityid]);
break;
}
}
}
}
}
$filteredData = [];
foreach ($metadata as $entityid => $idpentry) {
if (!empty($idpentry['name']) && is_array($idpentry['name'])) {
foreach ($idpentry['name'] as $key => $value) {
if (str_contains($transliterator->transliterate($value), $transliterator->transliterate($searchTerm))) {
$filteredData[$entityid] = $idpentry;
break;
if (!empty($searchBox['exclude']['registration_authorities']) && !empty($metadata[$entityid])) {
foreach ($searchBox['exclude']['registration_authorities'] as $registrationAuthority) {
if ($registrationAuthority === $idpentry['RegistrationInfo']['registrationAuthority']) {
unset($metadata[$entityid]);
break;
}
}
}
}
if (!in_array($idpentry, $filteredData, true) && !empty($idpentry['description']) && is_array(
$idpentry['description']
)) {
foreach ($idpentry['description'] as $key => $value) {
if (str_contains($transliterator->transliterate($value), $transliterator->transliterate($searchTerm))) {
$filteredData[$entityid] = $idpentry;
break;
if (!empty($searchBox['exclude']['upstream_idps']) && !empty($metadata[$entityid])) {
foreach ($searchBox['exclude']['upstream_idps'] as $upstreamIdp) {
if ($upstreamIdp === $entityid) {
unset($metadata[$entityid]);
break;
}
}
}
}
if (!in_array($idpentry, $filteredData, true) && !empty($idpentry['url']) && is_array($idpentry['url'])) {
foreach ($idpentry['url'] as $key => $value) {
if (str_contains(strtolower($value), strtolower($searchTerm))) {
$filteredData[$entityid] = $idpentry;
break;
}
}
if ($skipMatching) {
$filteredData = $metadata;
} else {
$filteredData = Campusidp::getIdpsMatchedBySearchTerm($metadata, $searchTerm);
}
}
......@@ -129,8 +114,8 @@ foreach ($filteredData as $entityid => $idpentry) {
$item['idpentityid'] = $entityid;
$item['image'] = $searchBox['logos'][$entityid] ?? Campusidp::getMostSquareLikeImg($idpentry);
if (!empty($idpentry['name'][$_GET['language']])) {
$item['text'] = $idpentry['name'][$_GET['language']];
if (!empty($idpentry['name'][$language])) {
$item['text'] = $idpentry['name'][$language];
} elseif (!empty($idpentry['name']['en'])) {
$item['text'] = $idpentry['name']['en'];
} elseif (reset($idpentry['name'])) {
......
......@@ -77,6 +77,7 @@ function selectizeLoad(query, callback) {
data: {
q: query,
index: this.settings.myIndex,
idphint: this.settings.idphint,
language: document.documentElement.getAttribute("lang"),
page_limit: 10,
},
......@@ -167,6 +168,9 @@ document.addEventListener("DOMContentLoaded", function () {
create: false,
maxItems: 1,
myIndex: index,
idphint: JSON.parse(
document.querySelector('meta[name="idphint"]').content
),
loadThrottle: 250,
placeholder: placeholderTexts[index] ?? defaultPlaceholder,
render: {
......
......@@ -34,9 +34,43 @@ if (array_key_exists('aarc_idp_hint', $state)) {
if (array_key_exists('idphint', $state)) {
$parts = explode(',', $state['idphint']);
if (count($parts) === 1) {
$state['saml:idp'] = urldecode($parts[0]);
Campusidp::delegateAuthentication($state[Campusidp::SP_SOURCE_NAME], $state);
} else {
$idphint = [];
foreach ($parts as $part) {
$idphint[] = urldecode($part);
}
if (count($idphint) <= Campusidp::IDP_HINT_BUTTONS_LIMIT) {
$ch = curl_init();
curl_setopt(
$ch,
CURLOPT_URL,
Module::getModuleURL('campusmultiauth/idpSearch.php?idphint=' . json_encode($idphint))
);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$idpsAsConfigItems = json_decode(curl_exec($ch));
curl_close($ch);
$hintComponentConfig = [];
$hintComponentConfig['name'] = 'individual_identities';
$hintComponentConfig['priority'] = 'primary';
$hintComponentConfig['title'] = '';
$hintComponentConfig['number_shown'] = Campusidp::IDP_HINT_BUTTONS_LIMIT;
for ($i = 0; $i < count($idpsAsConfigItems->items); $i++) {
$hintComponentConfig['identities'][$i] = [
'name' => $idpsAsConfigItems->items[$i]->text,
'logo' => $idpsAsConfigItems->items[$i]->image,
'upstream_idp' => $idpsAsConfigItems->items[$i]->idpentityid,
];
}
}
}
}
......@@ -141,6 +175,27 @@ $t->data['user_pass_source_name'] = $state[Campusidp::USER_PASS_SOURCE_NAME];
$t->data['sp_source_name'] = $state[Campusidp::SP_SOURCE_NAME];
$t->data['cookie_username'] = Campusidp::getCookie(Campusidp::COOKIE_USERNAME);
$t->data['cookie_password'] = Campusidp::getCookie(Campusidp::COOKIE_PASSWORD);
if (!empty($idphint)) {
$t->data['idphint'] = $idphint;
if (empty($hintComponentConfig)) {
$searchboxesToDisplay = Campusidp::findSearchboxesToDisplay($idphint, $wayfConfig);
$individualIdentitiesToDisplay = Campusidp::findIndividualIdentitiesToDisplay($idphint, $wayfConfig);
$t->data['searchboxes_to_display'] = $searchboxesToDisplay;
$t->data['individual_identities_to_display'] = $individualIdentitiesToDisplay;
$t->data['or_positions'] = Campusidp::getOrPositions(
$searchboxesToDisplay,
$individualIdentitiesToDisplay,
$idphint,
$wayfConfig
);
} else {
$t->data['hint_component_config'] = $hintComponentConfig;
}
}
$t->data['searchbox_indexes'] = json_encode(array_values(array_filter(array_map(function ($config, $index) {
return $config['name'] === 'searchbox' ? $index : null;
}, $wayfConfig['components'], array_keys($wayfConfig['components'])), function ($a) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment