<?php

/**
 * @file
 * The Social core module.
 */

use Drupal\block\Entity\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Field\FieldItemList;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Render\Element;
use Drupal\file\Entity\File;
use Drupal\filter\FilterFormatInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\filter\Entity\FilterFormat;
use Drupal\group\Entity\Group;
use Drupal\group\Entity\GroupInterface;
use Drupal\image\Entity\ImageStyle;
use Drupal\menu_link_content\MenuLinkContentInterface;
use Drupal\node\NodeInterface;
use Drupal\path\Plugin\Field\FieldWidget\PathWidget;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\block\BlockInterface;

/**
 * Implements hook_page_attachments().
 */
function social_core_page_attachments(array &$attachments) {
  // Unconditionally attach library to the page.
  $attachments['#attached']['library'][] = 'social_core/accessibility';

  // The library with logo improvements on the top of the gin toolbar.
  $attachments['#attached']['library'][] = 'social_core/admin-toolbar';
}

/**
 * Implements hook_theme().
 */
function social_core_theme() {
  return [
    // @deprecated: use node--hero.html.twig instead.
    'page_hero_data' => [
      'variables' => [
        'title' => NULL,
        'hero_node' => NULL,
        'node' => NULL,
        'node_type' => NULL,
        'section_class' => NULL,
        'event_enrollment' => NULL,
        'hero_styled_image_url' => NULL,
      ],
    ],
  ];
}

/**
 * Implements hook_module_implements_alter().
 */
function social_core_module_implements_alter(array &$implementations, string $hook): void {
  switch ($hook) {
    // See field_group_module_implements_alter()
    // They are forcing the form_alter() to be executed last, Open Social
    // has multiple form_alters that relies on #fieldgroup attribute so its very
    // important that the field_group_form_alter() is executed before them.
    case 'form_alter':
      $altered_implementations = [
        'field_group' => FALSE,
      ];
      unset($implementations['field_group']);
      $implementations = array_merge($altered_implementations, $implementations);
      break;
  }
}

/**
 * Implements hook_modules_installed().
 */
function social_core_modules_installed($modules) {
  // If either social_group_flexible_group or social_landing_page gets enabled
  // and the other is already enabled, we should import the the configuration
  // for the optional view mode by enabling social_flexible_group_featured.
  if ((in_array('social_group_flexible_group', $modules, TRUE) && \Drupal::moduleHandler()
    ->moduleExists('social_landing_page')) ||
    (in_array('social_landing_page', $modules, TRUE) && \Drupal::moduleHandler()
      ->moduleExists('social_group_flexible_group'))) {
    \Drupal::service('module_installer')
      ->install(['social_flexible_group_featured']);
  }
  // If either social_book or social_landing_page gets enabled and
  // the other is already enabled, we should import the the configuration
  // for the optional view mode by enabling social_book_featured.
  if ((in_array('social_book', $modules, TRUE) && \Drupal::moduleHandler()
    ->moduleExists('social_landing_page')) ||
    (in_array('social_landing_page', $modules, TRUE) && \Drupal::moduleHandler()
      ->moduleExists('social_book'))) {
    \Drupal::service('module_installer')->install(['social_book_featured']);
  }
}

/**
 * Implements hook_entity_form_display_alter().
 */
function social_core_entity_form_display_alter(EntityFormDisplayInterface $form_display, array $context) {
  $field_groups = $form_display->getThirdPartySettings('field_group');

  if (!isset($field_groups['group_settings'])) {
    return;
  }

  $fields = [];

  // List of all fields which are split by section.
  foreach ($field_groups as $field_group) {
    $fields = array_merge($fields, $field_group['children']);
  }

  // List of all fields which aren't split by section.
  $fields = array_diff(array_keys($form_display->getComponents()), $fields);

  if ($fields) {
    $field_groups['group_settings']['children'] = array_merge(
      $field_groups['group_settings']['children'],
      $fields
    );

    // This is intended to move the already existing "other fields" in the
    // Settings section, only if they are not part of any field group.
    // As this happens in an alter, we don't need to save our
    // ThirdPartySettings.
    // @see \Drupal\Core\Entity\EntityInterface::save().
    $form_display->setThirdPartySetting('field_group', 'group_settings', $field_groups['group_settings']);
  }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function social_core_preprocess_node(&$variables) {
  /** @var \Drupal\node\NodeInterface $node */
  $node = $variables['node'];
  // Add common variable for teaser images.
  $image_field = "field_{$node->getType()}_image";
  // If machine name too long or using another image field.
  if (!$node->hasField($image_field)) {
    $node_fields = $node->getFields();
    $find_image_field = '_social_core_find_image_field';
    assert(is_callable($find_image_field));
    /** @var callable(Drupal\Core\Field\FieldItemListInterface<Drupal\Core\Field\FieldItemInterface>):bool|null $find_image_field */
    $image_fields = array_filter($node_fields, $find_image_field);

    // Get the first image field of all the fields.
    if ($image_fields) {
      $image_field = reset($image_fields)->getName();
    }
    else {
      $image_field = NULL;
    }
  }
  if (!empty($variables['content'][$image_field]['#theme'])) {
    $variables['node_image'] = $variables['content'][$image_field];
    $variables['no_image'] = FALSE;
  }
  else {
    $variables['no_image'] = TRUE;
  }

  if ($variables['view_mode'] === 'hero') {
    // Add node edit url for management.
    // Get the current route name to check if the user is on the
    // edit or delete page.
    $route = \Drupal::routeMatch()->getRouteName();
    if (!in_array($route, [
      'entity.node.edit_form',
      'entity.node.delete_form',
    ])) {
      $account = \Drupal::currentUser();
      if ($node->access('update', $account)) {
        $variables['node_edit_url'] = $node->toUrl('edit-form')->toString();
        // Ensure the cache varies correctly depending upon access of the user.
        $variables['#cache']['contexts'][] = 'user.node_grants';
      }
    }

    // Add the hero styled image.
    if ($node->hasField($image_field) && !empty($node->{$image_field}->entity)) {
      $variables['hero_styled_image_url'] = ImageStyle::load('social_xx_large')
        ->buildUrl($node->{$image_field}->entity->getFileUri());
    }
  }
}

/**
 * Implements hook_social_user_account_header_account_links().
 *
 * Adds the "My invitations" link to the user menu.
 */
function social_core_social_user_account_header_account_links(array $context) {
  // We require a user for this link.
  if (empty($context['user']) || !($context['user'] instanceof AccountInterface)) {
    return [];
  }
  /** @var \Drupal\social_core\InviteService $core_invites */
  $core_invites = \Drupal::service('social_core.invite');
  // Only when there are actual Invite plugins enabled.
  if (!empty($core_invites->getInviteData('name'))) {
    $title = new TranslatableMarkup('Invites');
    // If we have invites show the number.
    if (!empty($core_invites->getInviteData('amount'))) {
      $title = [
        '#type' => 'inline_template',
        '#template' => '<span>{% trans %}Invites{% endtrans %}</span> <span{{ attributes }}>{{ icon }}</span>',
        '#context' => [
          'attributes' => new Attribute(['class' => 'margin-left-xs badge badge-accent badge--pill']),
          'icon' => (string) $core_invites->getInviteData('amount'),
        ],
      ];
    }

    $link = Link::createFromRoute($title, 'social_core.my_invites');

    return [
      'divider_no_mobile' => [
        '#wrapper_attributes' => [
          'class' => ['divider', 'hidden-for-phone-only'],
          'role' => 'separator',
        ],
        '#weight' => 325,
      ],
      'my_invites' => [
        '#type' => 'link',
        '#attributes' => [
          'title' => new TranslatableMarkup('Invites'),
        ],
        '#weight' => 350,
        '#access' => $link->getUrl()->access(),
      ] + $link->toRenderable(),
    ];
  }
}

/**
 * Implements hook_social_user_account_header_items().
 *
 * Adds an indicator to the user account menu.
 */
function social_core_social_user_account_header_items_alter(array &$menu_links, array $context) {
  // We require a logged in user for this indicator.
  if (empty($context['user']) || !$context['user']->isAuthenticated()) {
    return;
  }

  // If the account_box link was removed we have nothing to do.
  if (!isset($menu_links['account_box'])) {
    return;
  }

  // Get the total amount of notifications for the user.
  /** @var \Drupal\social_core\InviteService $core_invites */
  $core_invites = \Drupal::service('social_core.invite');
  // Only when there are actual Invites.
  if ($core_invites->getInviteData('amount') > 0) {
    $menu_links['account_box']['#wrapper_attributes']['class'][] = 'has-alert has-alert--desktop';
  }
}

/**
 * Tries to determine if a field is an image field based on a field name.
 *
 * @param \Drupal\Core\Field\FieldItemList $field
 *   A field object.
 *
 * @return \Drupal\Core\Field\FieldItemList|null
 *   Will return the FieldItemList or null if its not an image field.
 */
function _social_core_find_image_field(FieldItemList $field) {
  if (strpos($field->getName(), 'image') !== FALSE) {
    return $field;
  }
  return NULL;
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * For Site information Site details form.
 */
function social_core_form_system_site_information_settings_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Hide site slogan field and make it disabled as well whenever the user
  // is able to fill out the textfield.
  if (!empty($form['site_information']['site_slogan'])) {
    $form['site_information']['site_slogan']['#type'] = 'hidden';
    $form['site_information']['site_slogan']['#disabled'] = TRUE;
  }
}

/**
 * Implements hook_node_links_alter().
 */
function social_core_node_links_alter(array &$links, NodeInterface $entity, array &$context) {
  // Remove the comment links.
  unset($links['comment__field_topic_comments'], $links['comment__field_event_comments']);

  if (isset($context['view_mode']) && in_array($context['view_mode'], [
    'activity',
    'activity_comment',
  ])) {
    // Add a readmore link.
    $label = $entity->label() instanceof TranslatableMarkup ? $entity->label()->render() : $entity->label();
    $label = $label ?? '';
    $node_title_stripped = strip_tags($label);
    $links['node']['#links']['node-readmore'] = [
      'title' => t('Read more<span class="visually-hidden"> about @title</span>', [
        '@title' => $node_title_stripped,
      ]),
      'url' => $entity->toUrl(),
      'attributes' => [
        'rel' => 'tag',
        'title' => $node_title_stripped,
      ],
    ];
  }
}

/**
 * Prepares variables for comment field templates.
 *
 * Default template: field--comment.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - element: An associative array containing render arrays for the list of
 *     comments, and the comment form. Array keys: comments, comment_form.
 */
function social_core_preprocess_field(array &$variables) {
  $element = $variables['element'];
  if ($element['#formatter'] == 'comment_node') {
    // Create separate variables for the more_link.
    $variables['more_link'] = $element[0]['more_link'];
  }
}

/**
 * Implements hook_preprocess_block().
 */
function social_core_preprocess_block(&$variables) {
  /** @var \Drupal\user\Entity\User|int $account */
  $account = \Drupal::routeMatch()->getParameter('user');
  /** @var \Drupal\group\Entity\Group|int $group */
  $group = \Drupal::routeMatch()->getParameter('group');

  if (is_numeric($account)) {
    $account = User::load($account);
  }

  if (is_numeric($group)) {
    $group = Group::load($group);
  }

  if (!empty($variables['elements']['content']['#view'])) {
    $view = $variables['elements']['content']['#view'];

    if (!empty($view->getDisplay())) {
      $link = $view->getDisplay();

      if (!empty($link->useMoreText())) {
        $more_link = $link->useMoreText();
      }
    }
  }

  // Add variables to sidebar blocks.
  switch ($variables['elements']['#derivative_plugin_id']) {
    case 'upcoming_events-block_my_upcoming_events':
      $variables['view_all_path'] = Url::fromRoute('view.events.events_overview', [
        'user' => \Drupal::currentUser()->id(),
      ]);
      $variables['button_text'] = $more_link;
      $link->setOption('use_more', FALSE);
      break;

    case 'upcoming_events-block_community_events':
      $variables['subtitle'] = t('in the community');
      $variables['view_all_path'] = Url::fromRoute('view.upcoming_events.page_community_events');
      if (isset($variables['label']['#markup'])) {
        $label = $variables['label']['#markup'];
      }
      else {
        $label = t('events');
      }
      $variables['button_text'] = t('All @label', [
        '@label' => $label,
      ]);
      break;

    case 'latest_topics-block_latest_topics':
      $variables['subtitle'] = t('in the community');
      $variables['view_all_path'] = Url::fromRoute('view.latest_topics.page_latest_topics');
      $variables['button_text'] = $more_link;
      $link->setOption('use_more', FALSE);
      break;

    case 'newest_groups-block_newest_groups':
      $variables['subtitle'] = t('in the community');
      $variables['view_all_path'] = Url::fromRoute('view.newest_groups.page_all_groups');
      $variables['button_text'] = $more_link;
      $link->setOption('use_more', FALSE);
      break;

    case 'newest_users-block_newest_users':
      $variables['subtitle'] = t('in the community');
      $variables['view_all_path'] = Url::fromRoute('view.newest_users.page_newest_users');
      $variables['button_text'] = $more_link;
      $link->setOption('use_more', FALSE);
      break;

    case 'events-block_events_on_profile':
      if ($account instanceof UserInterface) {
        $variables['subtitle'] = t('for this user');
        $variables['view_all_path'] = Url::fromRoute('view.events.events_overview', [
          'user' => $account->id(),
        ]);
        $variables['button_text'] = t('All @label', [
          '@label' => $variables['label']['#markup'] ?? 'Events',
        ]);
      }
      break;

    case 'topics-block_user_topics':
      if ($account instanceof UserInterface) {
        $variables['subtitle'] = t('for this user');
        $variables['view_all_path'] = Url::fromRoute('view.topics.page_profile', [
          'user' => $account->id(),
        ]);
        $variables['button_text'] = $more_link;
        $link->setOption('use_more', FALSE);
      }
      break;

    case 'groups-block_user_groups':
      if ($account instanceof UserInterface) {
        $variables['subtitle'] = t('for this user');
        $variables['view_all_path'] = Url::fromRoute('view.groups.page_user_groups', [
          'user' => $account->id(),
        ]);
        $variables['button_text'] = $more_link;
        $link->setOption('use_more', FALSE);
      }
      break;

    case 'group_members-block_newest_members':
      if ($group instanceof GroupInterface) {
        $variables['subtitle'] = t('in the group');
        $variables['view_all_path'] = Url::fromRoute('view.group_members.page_group_members', [
          'group' => $group->id(),
        ]);
        $variables['button_text'] = $more_link;
        $link->setOption('use_more', FALSE);
      }
      break;

    case 'upcoming_events-upcoming_events_group':
      if ($group instanceof GroupInterface) {
        $variables['subtitle'] = t('in the group');
        $variables['view_all_path'] = Url::fromRoute('view.group_events.page_group_events', [
          'group' => $group->id(),
        ]);
        $variables['button_text'] = t('All @label', [
          '@label' => $variables['label']['#markup'],
        ]);
      }
      break;

    case 'latest_topics-group_topics_block':
      if ($group instanceof GroupInterface) {
        $variables['subtitle'] = t('in the group');
        $variables['view_all_path'] = Url::fromRoute('view.group_topics.page_group_topics', [
          'group' => $group->id(),
        ]);
        $variables['button_text'] = $more_link;
        $link->setOption('use_more', FALSE);
      }
      break;
  }
}

/**
 * Implements hook_menu_local_tasks_alter().
 */
function social_core_menu_local_tasks_alter(&$data, $route_name) {

  if (\Drupal::currentUser()->isAnonymous()) {
    // Anonymous user so we unset the user tabs on login register etc.
    if (isset($data['tabs'][0]['user.register'])) {
      unset($data['tabs'][0]['user.register']);
    }
    if (isset($data['tabs'][0]['user.pass'])) {
      unset($data['tabs'][0]['user.pass']);
    }
    if (isset($data['tabs'][0]['user.login'])) {
      unset($data['tabs'][0]['user.login']);
    }
  }

  // Remove node Edit tab. Edit will always go through Floating Edit Button.
  if (isset($data['tabs'][0]['entity.node.edit_form'])) {
    unset($data['tabs'][0]['entity.node.edit_form']);
  }

  // Change the default 'View' tab title.
  if (isset($data['tabs'][0]['entity.node.canonical']['#link'])) {
    $data['tabs'][0]['entity.node.canonical']['#link']['title'] = t('Details');
  }

  // Remove Delete tab. Delete will always go through Edit.
  if (isset($data['tabs'][0]['entity.node.delete_form'])) {
    unset($data['tabs'][0]['entity.node.delete_form']);
  }

  // Remove Revisions tab if revisions for the node not exist.
  $node = \Drupal::routeMatch()->getParameter('node');
  if ($node instanceof NodeInterface) {
    $revisions = \Drupal::entityTypeManager()->getStorage('node')->revisionIds($node);

    if ($revisions && count($revisions) === 1) {
      unset($data['tabs'][0]['entity.node.version_history']);
    }
  }

}

/**
 * Implements hook_form_taxonomy_vocabulary_form_alter().
 */
function social_core_form_taxonomy_vocabulary_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Fetch current user.
  $account = \Drupal::currentUser();

  // Check if the current use is the admin.
  if (!in_array('administrator', $account->getRoles())) {
    // Remove the option to delete.
    unset($form['actions']['delete']);
  }
}

/**
 * Implements hook_form_taxonomy_vocabulary_confirm_delete_alter().
 */
function social_core_form_taxonomy_vocabulary_confirm_delete_alter(&$form, FormStateInterface $form_state, $form_id) {

  // Fetch current user.
  $account = \Drupal::currentUser();

  // Check if the current use is the admin.
  if (!in_array('administrator', $account->getRoles())) {
    // Remove the option to delete.
    unset($form['actions']['submit']);
    $form['description']['#markup'] = t('You have insufficient permissions to delete this vocabulary');
  }
}

/**
 * Implements hook_preprocess().
 */
function social_core_preprocess_breadcrumb(&$variables) {
  // Get the user.
  $user = \Drupal::currentUser();
  // Add cache tag for the user.
  $variables['#cache']['tags'][] = 'user:breadcrumb:' . $user->id();
}

/**
 * Implements hook_element_info_alter().
 */
function social_core_element_info_alter(array &$info) {
  if (isset($info['text_format']['#process'])) {
    $info['text_format']['#process'][] = 'social_core_filter_process_format';
  }

  if (isset($info['entity_autocomplete'])) {
    $info['entity_autocomplete']['#value_callback'] = [
      '\Drupal\social_core\Entity\Element\EntityAutocomplete',
      'valueCallback',
    ];
  }
}

/**
 * Remove ability of selecting format if full_html is available.
 *
 * @todo Instead of defining the list of fields it would be better to add a
 *   separate Setting to all text_format fields, allowing to select whether to
 *   show format selector or not.
 */
function social_core_filter_process_format($element) {

  // Only fields listed here will have text format settings disabled.
  $full_html_field_ids = [
    'edit-body-0',
    'edit-field-profile-self-introduction-0',
    'edit-field-group-description-0',
  ];

  $element_id = $element['#id'];

  // Check if there is generated sub-id and cleanup it.
  if (($sub_id = strpos($element_id, '--')) !== FALSE) {
    $element_id = substr($element_id, 0, $sub_id);
  }

  // Default filter format for text area fields.
  $social_filter_format = 'full_html';

  // Allow module to override default filter format because some content type
  // could require specific one.
  Drupal::moduleHandler()->alter('social_filter_format_default', $social_filter_format);

  /** @var \Drupal\filter\Entity\FilterFormat $social_filter_format */
  $social_filter_format = FilterFormat::load($social_filter_format);

  // Check if filter format was loaded, to prevent fatal errors if wrong value
  // was provided in the hook_social_filter_format_default_alter().
  if (!$social_filter_format instanceof FilterFormatInterface) {
    return $element;
  }

  $permission_name = $social_filter_format->getPermissionName();
  $account = Drupal::currentUser();

  if (
    $element['#type'] === 'text_format' &&
    $element['#format'] === $social_filter_format->id() &&
    !$account->hasPermission($permission_name)
  ) {
    $element['#format'] = 'basic_html';
    $element['value']['#format'] = 'basic_html';
    $element['format']['format']['#default_value'] = 'basic_html';
    $element['format']['format']['#value'] = 'basic_html';
    $element['value']['#disabled'] = FALSE;
    $element['format']['format']['#access'] = FALSE;
    $element['format']['#access'] = TRUE;
    $key = array_search('filter_form_access_denied', $element['value']['#pre_render']);

    if (isset($element['value']['#pre_render'][$key])) {
      unset($element['value']['#pre_render'][$key]);
    }
  }
  elseif (
    $element['#type'] === 'text_format' &&
    $account->hasPermission($permission_name) &&
    in_array($element_id, $full_html_field_ids)
  ) {
    $element['#format'] = $social_filter_format;
    $element['format']['format']['#access'] = FALSE;
    $element['format']['format']['#value'] = $social_filter_format->id();
    $element['format']['help']['#access'] = FALSE;
    $element['format']['format']['#options'] = [
      $social_filter_format->id() => $social_filter_format->label(),
    ];
  }

  return $element;
}

/**
 * Hide blocks on extendable visibility path.
 *
 * Implements hook_block_access().
 */
function social_core_block_access(Block $block, $operation, AccountInterface $account) {
  $blocks = &drupal_static(__FUNCTION__, []);

  // We only care about view operations right now.
  if ($operation !== 'view') {
    return AccessResult::neutral();
  }

  // If we didnt have statically cached blocks,
  // lets see if we can grab it from the cache instead.
  if (empty($blocks)) {
    // First check if we have it cached before we run the modulehandler alter.
    /** @var \Drupal\Core\Cache\CacheBackendInterface $cache */
    $cache = \Drupal::service('cache.default');
    if ($cache->get('social_core.block_visibility_cache') === FALSE) {
      // Allow other modules to change the block visibility.
      $blocks = \Drupal::moduleHandler()->invokeAll('social_core_block_visibility_path');

      // Set the cache expire to 1 week.
      $date = new DateTime(date('Y-m-d'));
      $date->modify('+7 day');

      $cache->set('social_core.block_visibility_cache', $blocks, $date->getTimestamp(), ['social_core.block_visibility_cache']);
    }
    else {
      $blocks = $cache->get('social_core.block_visibility_cache')->data;
    }
  }

  // Alter the access based on the blocks array.
  if ($operation === 'view' && isset($blocks[$block->getPluginId()])) {
    // Get the current path.
    $current_path = \Drupal::service('path.current')->getPath();

    // Go through the request paths and see if they match.
    foreach ($blocks[$block->getPluginId()] as $page) {
      if (\Drupal::service('path.matcher')->matchPath($current_path, $page) === TRUE) {
        return AccessResult::forbidden();
      }
    }
  }

  // Make sure that socialblue_pagetitleblock_content doesn't show on nodes
  // pages. We can't add visibility 'entity_bundle:node' whether 'group_type'
  // since the block will disappear from pages like Access denied and some tests
  // will be failed. So, let's hide the block for node canonical instead.
  if (
    $operation === 'view' &&
    $block->getPluginId() === 'social_page_title_block' &&
    $block->id() === 'socialblue_pagetitleblock_content'
  ) {
    /** @var \Drupal\node\Entity\Node $node */
    $node = \Drupal::routeMatch()->getParameter('node');
    // Get the route name.
    $route_name = \Drupal::routeMatch()->getRouteName();

    // If it is a node we prevent the block from showing.
    if (
      $node instanceof NodeInterface &&
      $route_name === 'entity.node.canonical'
    ) {
      return AccessResult::forbidden();
    }
  }

  // No opinion.
  return AccessResult::neutral();
}

/**
 * Implements hook_field_widget_single_element_form_alter().
 */
function social_core_field_widget_single_element_form_alter(&$element, FormStateInterface $form_state, $context) {
  $field_definition = $context['items']->getFieldDefinition();

  if ($field_definition->getType() === 'path') {
    $url = Url::fromRoute('<front>', [], [
      'absolute' => TRUE,
    ]);

    // Protocols we can strip.
    $protocols = [
      'https://www.',
      'http://www.',
      'https://',
      'http://',
      'www.',
    ];

    // The URL.
    $truncate_url = $url->toString();
    // Loop over the protocols and try to replace them in the string.
    foreach ($protocols as $protocol) {
      $truncate_url = str_replace($protocol, '', $truncate_url);
    }
    // Put the protocoless, truncated URL in the addon.
    $element['alias']['#field_prefix'] = '<div class="input-group input-group-expanded"><span class="input-group-addon">' . $truncate_url . '</span>';
    $element['alias']['#field_suffix'] = '</div>';

    // Unset description and title.
    unset($element['alias']['#description']);
    unset($element['alias']['#title']);

    // URLs are stored with a leading slash but our display includes that slash
    // in the prefix so we remove it here.
    if (isset($element['alias']['#default_value'])) {
      $element['alias']['#default_value'] = ltrim($element['alias']['#default_value'], '/');
    }

    $element['#element_validate'] = ['social_core_path_validate'];
  }
  if ($field_definition->getType() === 'address') {
    $default_country = \Drupal::config('system.date')->get('country.default');
    $element['address']['#default_value']['country_code'] = $element['address']['#default_value']['country_code'] ?? $default_country;
  }
}

/**
 * Form element validation handler for URL alias form element.
 *
 * Ensures that a path alias entered by a user is always a relative internal URL
 * that starts with a leading slash. If the user did not enter a leading slash
 * then we add it for them.
 *
 * @param array $element
 *   The form element.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state.
 */
function social_core_path_validate(array &$element, FormStateInterface $form_state) {
  $alias = trim($element['alias']['#value'], " \\/");
  $parsed_url = parse_url($alias);

  if (isset($parsed_url['host']) || isset($parsed_url['scheme']) || !isset($parsed_url['path'])) {
    $form_state->setError($element, t('The URL alias must be a relative URL.'));
  }
  // We know that a user entered '/' is a valid alias according to Drupal so we
  // do no further validation.
  elseif (trim($element['alias']['#value']) !== "/") {
    // $alias is already trimmed of leading/trailing slashes.
    // We use rtrim here to ensure we don't set the value to an empty slash
    // during form submission.
    // The following line only changes the value of the rendered form element.
    // If we have a non-empty alias PathWidget::validateFormElement will
    // update the actual stored value to include the leading slash.
    $element['alias']['#value'] = rtrim('/' . $alias, '/');
    PathWidget::validateFormElement($element, $form_state);
  }
}

/**
 * Implements hook_form_alter().
 */
function social_core_form_alter(
  array &$form,
  FormStateInterface $form_state,
  string $form_id
): void {
  // Array of node types, which have the new group_settings fieldset,
  // these nodes can use the new content style, and have $form['advanced']
  // removed.
  // if we remove it for nodes which don't have that fieldset, fields will be
  // rendered outside of a card.
  $compatible_content_type_forms = [
    'node_event_form',
    'node_event_edit_form',
    'node_topic_form',
    'node_topic_edit_form',
    'node_page_form',
    'node_page_edit_form',
  ];

  // Create new alter hook which allows extensions to add nodes
  // to this list once they have added the group_settings.
  \Drupal::moduleHandler()->alter('social_core_compatible_content_forms', $compatible_content_type_forms);

  // Let's remove the form elements not needed by Open Social.
  // We understand this is quite BC breaking, url_redirects / revision info
  // we don't use as Open Social, you might use. So by
  // changing the theme setting in social blue, you can go back to the
  // old settings and use Field UI to change the other fields.
  $use_social_content_forms = theme_get_setting('content_entity_form_style');
  if ($use_social_content_forms === 'open_social' && in_array($form_id, $compatible_content_type_forms)) {
    $form_object = $form_state->getFormObject();

    if ($form_object instanceof EntityFormInterface) {
      $entity = $form_object->getEntity();
    }

    // We want to move all the fields from $form['advanced'] to our new
    // field sets which are created as part of #3186003.
    if (!empty($form['advanced'])) {
      unset($form['advanced']);

      // Unset optional fields.
      if (isset($form['revision'])) {
        unset($form['revision']);
      }

      if (isset($form['revision_log'])) {
        unset($form['revision_log']);
      }

      if (isset($form['revision_information'])) {
        unset($form['revision_information']);
      }

      if (isset($form['url_redirects'])) {
        unset($form['url_redirects']);
      }

      if (isset($form['meta'])) {
        unset($form['meta']);
      }

      // Add descriptive text to URL Alias field.
      if (isset($form['path']['widget'][0]['alias'])) {
        $form['path']['widget'][0]['alias']['#description'] = t('The URL alias allows you to customise the link to this page.');
      }

      // Hide the automatic URL alias.
      if (isset($form['path']['widget'][0]['pathauto'])) {
        $form['path']['widget'][0]['pathauto']['#type'] = 'hidden';
        // Path uses the above checkbox to decide whether or not to save
        // the automatically generated alias, or use a custom one.
        // since we hide the element, and the default is checked,
        // the path will never be overridden if someone updates it.
        // So we need to check this in our custom submit handler.
        // It should perform before path auto runs.
        array_unshift($form['actions']['submit']['#submit'], '_social_core_path_widget_submit');
      }

      // Move the fields from advanced to group_settings.
      if (!empty($form['#fieldgroups']['group_settings'])) {
        // Move the book outline fields to the group settings.
        // #502430 we need to remove this ourselves, users with permission
        // can see the book outline even when not configured as part
        // of the book settings.
        if (
          !empty($form['book']) &&
          isset($entity) &&
          \Drupal::moduleHandler()->moduleExists('book')
        ) {
          // Move it to our new fieldset on book pages either on its own
          // or in the group_settings.
          $form['book']['#type'] = 'fieldset';
          $form['book']['#attributes']['class'][] = 'card';
          $form['book']['#weight'] = isset($form['#fieldgroups']['group_visibility']->weight)
            ? $form['#fieldgroups']['group_visibility']->weight + 1 : '3';

          // If it's not enabled we can just safely unset it.
          if (!book_type_is_allowed($entity->bundle())) {
            unset($form['book']);
          }
        }

        // Move the node Menu settings to group_settings.
        if (!empty($form['menu'])) {
          $form['menu']['#group'] = 'group_settings';
          $form['menu']['#weight'] = -20;
          $form['menu']['#title'] = t('Menu link');
          if (!empty($form['menu']['link']['title'])) {
            $form['menu']['link']['title']['#title'] = t('Title');
            $form['menu']['link']['title']['#description'] = t('This title will be displayed in the menu.');
          }
          if (!empty($form['menu']['link']['description'])) {
            $form['menu']['link']['title']['#description'] = t('This description will be displayed when hovering over the menu link.');
          }
        }

        // Move the Published status to group settings with a new wrapper.
        if (!empty($form['status']) && Element::isVisibleElement($form['status'])) {
          // Add an details element to status.
          $form['status_label'] = [
            '#type' => 'details',
            '#title' => t('Publish status'),
            '#description' => '',
            '#open' => TRUE,
            '#weight' => 110,
            '#group' => 'group_settings',
          ];
          $form['status']['#group'] = 'status_label';
          $form['status_label']['status'] = $form['status'];
          unset($form['status']);
        }

        // Move author uid and/or created to the new author info fieldset,
        // and remove descriptive text.
        foreach (['uid', 'created'] as $key) {
          if (isset($form[$key]) && Element::isVisibleElement($form[$key])) {
            $field = &$form[$key];
            $field['#group'] = 'author_info';
            $field['widget']['#description'] = '';

            if ($key === 'uid') {
              if (!empty($field['widget'][0]['target_id'])) {
                $field['widget'][0]['target_id']['#description'] = '';
              }
            }
            else {
              $field['widget'][0]['#description'] = '';
              $field['widget'][0]['value']['#description'] = '';
            }

            $form['author_info'][$key] = $form[$key];

            unset($form[$key]);
          }
        }

        if (isset($form['author_info'])) {
          // Node author information for administrators.
          $form['author_info'] += [
            '#type' => 'details',
            '#title' => t('Author Information'),
            '#description' => '',
            '#open' => TRUE,
            '#weight' => 100,
            '#group' => 'group_settings',
          ];
        }

        // Add validation for created date.
        array_unshift($form['#validate'], '_social_core_created_date_validation');

        $has_option_fields = FALSE;

        foreach (['promote', 'sticky'] as $field) {
          if (isset($form[$field])) {
            $form['options'][$field] = $form[$field];
            unset($form[$field]);
            $has_option_fields = TRUE;
          }
        }

        // When the "Promoted to front page" field or the "Sticky at top of
        // lists" field is enabled then move their wrapper (the "Promotion
        // options" sub-section) to the "Settings" section.
        // @see \Drupal\node\NodeForm::form().
        if ($has_option_fields) {
          $form['options']['#group'] = 'group_settings';

          // Place the "Promotion options" sub-section after the "Publish
          // status" sub-section.
          $form['options']['#weight'] = 120;

          // Display sub-section as expanded.
          $form['options']['#optional'] = FALSE;
        }
      }

      // According to FileWidget.php we cant use a widget form alter.
      if (!empty($form['field_files']) && !empty($form['field_files']['widget']['#file_upload_title'])) {
        $form['field_files']['widget']['#file_upload_title'] = t('Attachments');
      }
    }

    // Change the button text from Save to Create topic / event etc.
    // Adds a cancel button.
    // Removes the preview button.
    if (!empty($form['actions']['submit']['#value']) && isset($entity)) {

      // Change the button text from Save to Create topic / event etc.
      /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */
      if ($form_object->getOperation() !== 'edit') {
        $definition = \Drupal::entityTypeManager()
          ->getDefinition($entity->getEntityTypeId());

        if ($definition !== NULL) {
          $bundle_entity_type = $definition->getBundleEntityType();

          if ($bundle_entity_type !== NULL) {
            $bundle_entity = \Drupal::entityTypeManager()
              ->getStorage($bundle_entity_type)
              ->load($entity->bundle());

            if (
              $bundle_entity !== NULL &&
              ($label = $bundle_entity->label()) !== NULL
            ) {
              $form['actions']['submit']['#value'] = t('Create @entity', [
                '@entity' => strtolower((string) $label),
              ]);
            }
          }
        }
      }

      // Adds cancel button.
      if (
        !isset($form['actions']['cancel']) &&
        $entity->getEntityTypeId() === 'node'
      ) {
        $form['actions']['cancel'] = [
          '#type' => 'submit',
          '#value' => t('Cancel'),
          '#submit' => ['_social_core_node_cancel'],
          '#limit_validation_errors' => [],
          '#attributes' => [
            'class' => ['button btn-default btn waves-effect waves-btn'],
          ],
        ];
      }

      // Remove the preview button.
      if (isset($form['actions']['preview'])) {
        unset($form['actions']['preview']);
      }
    }
  }
}

/**
 * Custom form validation handler for validating created date.
 *
 * @param array $form
 *   The form array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state.
 *
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function _social_core_created_date_validation(array $form, FormStateInterface $form_state): void {
  $created = $form_state->getValue('created');
  if (!$created) {
    return;
  }

  $date = end($created);
  /** @var \Drupal\Core\Datetime\DrupalDateTime $date */
  $date = $date['value'];

  if ($date->getTimestamp() > time()) {
    $form_state->setErrorByName('created', t('The created date cannot be in the future.'));
  }
}

/**
 * Custom form submit handler for handling an path alias update.
 *
 * @param array $form
 *   The form array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state.
 *
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function _social_core_path_widget_submit(array $form, FormStateInterface $form_state) {
  // Path uses the pathauto checkbox to decide whether or not to save
  // the automatically generated alias, or use a custom one.
  // since we hide the element, and the default is checked,
  // the path will never be overridden if someone updates it.
  // So we need to check this in our custom submit handler.
  $default_path = $form['path']['widget'][0]['alias']['#default_value'] ?? '';
  $input = $form_state->getUserInput();
  $updated_path = $input['path'][0]['alias'] ?? '';
  // If we don't have a match, make sure we override the checkbox as well.
  if ($updated_path !== $default_path) {
    // Override the User input, act as if this was changed.
    $input['path'][0]['pathauto'] = "0";
    $form_state->setUserInput($input);
    // Override the value.
    $form_state->setValue(['path', '0', 'pathauto'], "0");

    // If the updated path is empty, make it automatic again.
    if (empty($updated_path)) {
      $input['path'][0]['pathauto'] = "1";
      $form_state->setUserInput($input);
      $form_state->setValue(['path', '0', 'pathauto'], "1");
    }
  }
}

/**
 * Redirects to the previous page, or home when pressing cancel form.
 *
 * @param array $form
 *   Join and leave form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state interface.
 */
function _social_core_node_cancel(array &$form, FormStateInterface $form_state) {
  // By default the front page will be used,
  // especially for adding a node.
  $form_state->setRedirect('<front>');

  // If we have an existing node, we rather go to the node itself.
  if (!empty($form_state->getFormObject()->getEntity())) {
    $entity = $form_state->getFormObject()->getEntity();

    if ($entity instanceof NodeInterface && $entity->id() !== NULL) {
      $form_state->setRedirect(
        'entity.node.canonical',
        ['node' => $entity->id(), []]
      );
    }
  }
}

/**
 * Implements hook_theme_registry_alter().
 */
function social_core_theme_registry_alter(&$theme_registry) {
  $theme_registry['menu__toolbar']['variables']['menu_name'] = NULL;
}

/**
 * Function to set default email logo if not set already for OS.
 */
function _social_core_set_default_email_logo_for_socialblue() {
  // Only when socialblue is default we continue.
  if (\Drupal::configFactory()->get('system.theme')->get('default') === 'socialblue') {
    $logo = theme_get_setting('logo.url', 'socialblue');
    $basetheme_url = \Drupal::service('extension.list.theme')->getPath('socialblue');
    // Only if the theme setting for the logo, is still the socialblue
    // default logo, we can update our default logo.
    // If someone changed it to their own branding, let's not update it
    // because that would create regression.
    // that will be /profile/contrib/social/themes/socialblue/logo.svg.
    if (!empty($basetheme_url) && $logo === DIRECTORY_SEPARATOR . $basetheme_url . DIRECTORY_SEPARATOR . 'logo.svg') {
      // Add default image.
      $config_factory = \Drupal::configFactory();
      // Lets get the email logo settings.
      $socialblue_settings = $config_factory->getEditable('socialblue.settings');
      $default_image = $socialblue_settings->get('email_logo');

      // Only if there is no default image, or if its the same as the
      // socialblue logo we can continue.
      if (empty($default_image) || $default_image === $basetheme_url . DIRECTORY_SEPARATOR . 'logo.svg') {
        $file_system = \Drupal::service('file_system');
        $uri = $file_system->copy($basetheme_url . DIRECTORY_SEPARATOR . 'logo_email.png', 'public://logo_email.png', FileSystemInterface::EXISTS_REPLACE);

        $media = File::create([
          'uri' => $uri,
        ]);
        $media->setPermanent();
        $media->save();

        $default_image[0] = $media->id();
        // We just save the file id.
        $socialblue_settings->set('email_logo', $default_image)->save();
      }
    }
  }
}

/**
 * Used to set the favicon of GIN to open socials logo.
 */
function _social_core_set_favicon() {
  $config = \Drupal::configFactory()->getEditable('gin.settings');
  if (!empty($config->getRawData())) {
    $gin_config = $config->getRawData();
    // Update the favicon in GIN.
    $gin_config['favicon']['use_default'] = FALSE;
    $gin_config['favicon']['path'] = 'themes/contrib/socialblue/favicon.ico';
    $gin_config['favicon']['mimetype'] = 'image/vnd.microsoft.icon';
    $config->setData($gin_config);
    $config->save();
  }
}

/**
 * Implements hook_preprocess_HOOK() for status_messages.
 */
function social_core_preprocess_status_messages(&$variables) {
  // We want to remove stratus messages coming form image_widget_crop module.
  // These notifications are technical in nature and add no value for the user.
  // @see: \Drupal\image_widget_crop\ImageWidgetCropManager::saveCrop().
  if (isset($variables['message_list']['status'])) {
    // Get all the status messages.
    $status_messages = $variables['message_list']['status'];
    // Loop through them to find the relevant messages we are looking for.
    foreach ($status_messages as $delta => $message) {
      // Unset them finally.
      if (strpos((string) $message, 'The crop') !== FALSE) {
        unset($variables['message_list']['status'][$delta]);
      }
    }
  }
}

/**
 * Implements hook_preprocess_HOOK().
 *
 * Define a type of follow buttons as default here instead of the theme template
 * to be able to change the type by other modules. E.g., the profile preview
 * module change type of one from these buttons to primary.
 *
 * @see social_profile_preview_preprocess_flag()
 */
function social_core_preprocess_flag(array &$variables): void {
  $variables['attributes']['class'][] = 'btn-default';
}

/**
 * Get all default main menu links that ships with Open Social.
 */
function _social_core_default_main_menu_links() {
  // If user has a permission to bypass all the restrictions, then
  // we don't return any links.
  if (\Drupal::currentUser()->hasPermission('bypass social_core menu links access')) {
    return [];
  }

  $links = &drupal_static(__FUNCTION__);

  if (empty($links)) {
    $default_os_links = [
      'Home' => [
        'link' => 'internal:/',
        'menu_name' => 'main',
      ],
      'Explore' => [
        'link' => 'internal:',
        'menu_name' => 'main',
      ],
    ];

    $entity_type_manager = \Drupal::entityTypeManager();
    $menu_links = $entity_type_manager
      ->getStorage('menu_link_content')
      ->loadByProperties(['menu_name' => 'main']);

    $links = [];

    /** @var \Drupal\menu_link_content\MenuLinkContentInterface $link */
    foreach ($menu_links as $link) {
      if (!in_array($link->getTitle(), array_keys($default_os_links))) {
        continue;
      }

      $os_link = $default_os_links[$link->getTitle()];
      $link_uri = $link->get('link')->getValue();
      $link_uri = end($link_uri);
      $link_uri = $link_uri['uri'];
      if ($link_uri !== $os_link['link']) {
        continue;
      }

      if ($link->getMenuName() !== $os_link['menu_name']) {
        continue;
      }

      $links[] = $link;
    }
    // Enable modules to alter the list.
    \Drupal::moduleHandler()->invokeAll(
      'social_core_default_main_menu_links_alter',
      [&$links]
    );
  }

  return $links;
}

/**
 * Implements hook_entity_access().
 *
 * Forbid access to edit/delete form for default Open Social menu links.
 */
function social_core_entity_access(EntityInterface $entity, string $operation, AccountInterface $account) {
  $entity_type = $entity->getEntityTypeId();

  if (
    ($entity_type === 'menu_link_content')
    && $operation !== 'view'
    && $account->isAuthenticated()
    && !$account->hasPermission('bypass social_core menu links access')
    && $account->hasPermission('administer menu')
  ) {
    /** @var \Drupal\menu_link_content\MenuLinkContentInterface $entity */
    $name = $entity->getTitle();
    $link = $entity->get('link')->getValue();
    $link = end($link);

    $default_os_links = _social_core_default_main_menu_links();

    /** @var \Drupal\menu_link_content\MenuLinkContentInterface $os_link */
    foreach ($default_os_links as $os_link) {
      // Ensure that we have link instance.
      if (!($os_link instanceof MenuLinkContentInterface)) {
        continue;
      }

      $os_link_title = $os_link->getTitle();
      $os_link_uri = $entity->get('link')->getValue();
      $os_link_uri = end($os_link_uri)['uri'];
      if ($name === $os_link_title && $link['uri'] === $os_link_uri) {
        return AccessResult::forbidden();
      }
    }
  }
  return AccessResult::neutral();
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Remove operation links for main menu form for all default Open Social links.
 */
function social_core_form_menu_edit_form_alter(array &$form, FormStateInterface $form_state) {
  $menu = $form_state->getFormObject()->getEntity();
  if ($menu->id() !== 'main') {
    return;
  }

  $os_links = _social_core_default_main_menu_links();
  $os_links_ids = [];
  foreach ($os_links as $os_link) {
    $os_links_ids[] = "menu_plugin_id:menu_link_content:" . $os_link->uuid->value;
  }

  foreach ($os_links_ids as $menu_id) {
    unset($form['links']['links'][$menu_id]['operations']['#links']['edit']);
    unset($form['links']['links'][$menu_id]['operations']['#links']['delete']);
  }
}

/**
 * Add new paths where blocks should be displayed.
 *
 * @param string $pages
 *   The current sequence of block paths.
 * @param array $paths
 *   List of blocks which should be updated and paths which should be added.
 *
 * @return string
 *   List of block paths with new paths.
 */
function _social_core_blocks_paths(string $pages, array $paths): string {
  return $pages . "\r\n" . implode("\r\n", $paths);
}

/**
 * Removes paths where blocks should be displayed.
 *
 * @param string $pages
 *   The current sequence of block paths.
 * @param array $paths
 *   List of blocks which should be updated and paths which should be added.
 *
 * @return string
 *   List of block paths with new paths.
 */
function _social_core_blocks_unset_paths(string $pages, array $paths): string {
  $pages = explode("\r\n", $pages);

  foreach ($pages as $delta => $page) {
    if (in_array($page, $paths)) {
      unset($pages[$delta]);
    }
  }

  return implode("\r\n", $pages);
}

/**
 * Process list of pages where chosen blocks should be shown.
 *
 * @param array $items
 *   Paths of blocks.
 * @param callable|null $function
 *   (optional) The function which updates the pages set. Defaults to NULL.
 */
function _social_core_install_blocks(array $items, callable $function = NULL): void {
  if (!is_callable($function)) {
    $function = '_social_core_blocks_paths';
  }

  $storage = \Drupal::entityTypeManager()->getStorage('block');

  foreach ($items as $id => $paths) {
    $block = $storage->load($id);

    if (!$block instanceof BlockInterface) {
      continue;
    }

    /** @var \Drupal\system\Plugin\Condition\RequestPath $visibility */
    $visibility = $block->getVisibilityCondition('request_path');

    $configuration = $visibility->getConfiguration();
    $pages = $function($configuration['pages'], (array) $paths);

    if ($configuration['pages'] !== $pages) {
      $configuration['pages'] = $pages;
      $block->setVisibilityConfig('request_path', $configuration)->save();
    }
  }
}
