Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
limit_site_hierarchy: TRUE
9 changes: 9 additions & 0 deletions modules/tide_site_restriction/tide_site_restriction.install
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ function tide_site_restriction_install() {
TideSiteRestrictionOperation::addNecessarySettings();

}

/**
* Creates the initial configuration for site hierarchy restriction.
*/
function tide_site_restriction_update_10001() {
\Drupal::configFactory()->getEditable('tide_site_restriction.settings')
->set('limit_site_hierarchy', TRUE)
->save();
}
143 changes: 143 additions & 0 deletions modules/tide_site_restriction/tide_site_restriction.module
Original file line number Diff line number Diff line change
Expand Up @@ -478,3 +478,146 @@ function tide_site_restriction_field_widget_single_element_form_alter(&$element,
}
}
}

/**
* Implements hook_form_BASE_FORM_ID_alter() for node_form.
*
* Adds custom validation to enforce site hierarchy rules and handles
* auto-selection of site fields if the user only has access to a single site.
* Logic is bypassed for 'administrator' and 'site_admin' roles.
*
* @param array $form
* Nested array of form elements that comprise the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param string $form_id
* String representing the name of the form itself.
*/
function tide_site_restriction_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$config = \Drupal::config('tide_site_restriction.settings');
$is_enabled = $config->get('limit_site_hierarchy');

if ($is_enabled === FALSE) {
return;
}
$user = \Drupal::currentUser();
$roles = $user->getRoles();

$is_admin = array_intersect(['administrator', 'site_admin'], $roles);
if (!empty($is_admin)) {
return;
}

/** @var \Drupal\node\NodeInterface $node */
$node = $form_state->getFormObject()->getEntity();
$form['#validate'][] = '_tide_site_restriction_node_form_validate';

// Help text under the Site field heading (AC SD-1485).
if (isset($form['field_node_site']['widget'])) {
$form['field_node_site']['widget']['#description'] = t('You must select only one site section.');
}

// Skip auto-select on quick_node_clone forms — $node->isNew() is TRUE for
// clones too, but the form already carries field values copied from the
// source node. Forcing defaults here would wipe the cloned Site sections.
$is_clone_form = strpos($form_id, '_quick_node_clone_form') !== FALSE;

$options = $form['field_node_primary_site']['widget']['#options'] ?? [];
if (count($options) === 1 && $node->isNew() && !$is_clone_form) {
$target_site_id = key($options);

// Set the value for Primary Site.
$form['field_node_primary_site']['widget']['#default_value'] = $target_site_id;

// Set the value for the Site field.
if (isset($form['field_node_site'])) {
$form['field_node_site']['widget']['#default_value'] = [$target_site_id];
}
}
}

/**
* Custom validation handler for node forms to enforce site selection rules.
*
* This validator ensures that (per AC SD-1485):
* 1. At most one site may be selected in 'field_node_site' beyond the
* Primary Site itself — i.e. the user can pick the Primary plus one
* additional section, nothing more.
* 2. If a Root site is selected in 'field_node_site', it must match
* 'field_node_primary_site'.
* 3. Any selected Child sites in 'field_node_site' must be direct or indirect
* descendants of the 'field_node_primary_site'.
*
* @param array $form
* The form structure.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form, used to retrieve values and set errors.
*/
function _tide_site_restriction_node_form_validate($form, FormStateInterface $form_state) {
$primary_site = $form_state->getValue('field_node_primary_site');
$site_list = $form_state->getValue('field_node_site');

$primary_site_id = !empty($primary_site[0]['target_id']) ? $primary_site[0]['target_id'] : NULL;

if (!$site_list || !$primary_site_id) {
return;
}

$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$selected_root_id = NULL;
$root_count = 0;
$other_count = 0;
$invalid_children = [];

foreach ($site_list as $item) {
if (empty($item['target_id'])) {
continue;
}
$tid = $item['target_id'];

// AC SD-1485: count selections that are not the Primary Site. At most
// one such "other" selection is allowed.
if ($tid != $primary_site_id) {
$other_count++;
}

// loadAllParents() returns the term itself plus every ancestor, so a
// grandchild resolves to its full chain up to the root — needed to
// validate L1 + L3 selections where loadParents() only sees the L2
// immediate parent.
$ancestors = $term_storage->loadAllParents($tid);
unset($ancestors[$tid]);
if (empty($ancestors)) {
$root_count++;
$selected_root_id = $tid;
}
elseif (!isset($ancestors[$primary_site_id])) {
// Primary Site is not in this term's ancestor chain.
$invalid_children[] = $tid;
}
}

// AC SD-1485: max one site besides the Primary Site. Return early so the
// user sees a single, focused error instead of stacked messages when many
// sites are selected.
if ($other_count > 1) {
$form_state->setErrorByName('field_node_site', t('only one site can be selected, please select only one site'));
return;
}

// Multiple top-level sites selected (e.g. two L1 roots).
if ($root_count > 1) {
$form_state->setErrorByName('field_node_site', t('only one site can be selected, please select only one site'));
return;
}

// Ensures the "Root" selected in site matches "Primary Site" field.
if ($selected_root_id && $primary_site_id != $selected_root_id) {
$form_state->setErrorByName('field_node_site', t('The selected site must match the site selected in Primary Site field.'));
}

// Ensures the "Child" is actually a child of the "Primary Site".
if (!empty($invalid_children)) {
$form_state->setErrorByName('field_node_site', t('The selected site section does not belong to the selected Primary Site.'));
}
}
Loading