<?php

/**
 * @file
 * Install, update and uninstall functions for the social_profile module.
 */

use Drupal\Core\Config\FileStorage;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\Entity\EntityViewMode;
use Drupal\field\Entity\FieldConfig;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\profile\Entity\Profile;
use Drupal\profile\Entity\ProfileType;
use Drupal\user\RoleInterface;
use Symfony\Component\Yaml\Yaml;

/**
 * Implements hook_install().
 *
 * Perform actions related to the installation of social_profile.
 */
function social_profile_install() {
  // Grant the default permissions for this feature.
  user_role_grant_permissions(
    RoleInterface::AUTHENTICATED_ID,
    [
      'update own profile profile',
      'view own profile profile',
    ]
  );
  user_role_grant_permissions(
    'verified',
    [
      'view any profile profile',
    ]
  );
  user_role_grant_permissions(
    'contentmanager',
    [
      'view any profile profile',
      'update any profile profile',
      'edit profile tags',
    ]
  );
  user_role_grant_permissions(
    'sitemanager',
    [
      'view any profile profile',
      'update any profile profile',
      'edit profile tags',
      'delete terms in profile_tag',
      'edit terms in profile_tag',
      'administer profile settings',
      'view profile email',
      'view profile language',
    ]
  );
  // Add some links.
  $parents = \Drupal::entityTypeManager()->getStorage('menu_link_content')
    ->loadByProperties(['menu_name' => 'main', 'title' => 'Explore', 'expanded' => TRUE]);
  $parent = reset($parents);
  if ($parent !== FALSE) {
    MenuLinkContent::create([
      'title' => t('All members'),
      'link' => ['uri' => 'internal:/all-members'],
      'menu_name' => 'main',
      'expanded' => FALSE,
      'weight' => 50,
      'parent' => 'menu_link_content:' . $parent->uuid(),
    ])->save();
  }

  // Add default profile image.
  _social_profile_add_default_profile_image();

  // Create a profile for user 1.
  Profile::create([
    'type' => ProfileType::load('profile')->id(),
    'uid' => 1,
  ])->save();
}

/**
 * Function to set default profile image if not set already.
 */
function _social_profile_add_default_profile_image() {
  $field_image_config = FieldConfig::load('profile.profile.field_profile_image');
  assert($field_image_config !== NULL, "Trying to set default image for missing field, this is an error in Open Social.");
  $field_image_config_uuid = $field_image_config->uuid();
  assert($field_image_config_uuid !== NULL, "Field config that was loaded is unsaved.");

  // Copy the file from our module to the file system. File::create also accepts
  // a URI directly in our module but that might cause the image to be deleted
  // from our module which means we have to make a copy.
  $file_system = \Drupal::service('file_system');
  $scheme = $field_image_config->getFieldStorageDefinition()->getSetting("uri_scheme");
  $directory = "$scheme://default_images/";
  $file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
  $uri = $file_system->copy(
    \Drupal::service('extension.list.module')->getPath('social_profile') . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'default-profile-picture.png',
    // The image must be in `default_images/` since this folder is
    // special-cased to still allow viewing when in the private filesystem.
    "{$directory}default-profile-picture.png",
    FileSystemInterface::EXISTS_REPLACE
  );

  // Create a file with the UUID that was used in the configuration so the field
  // config actually works.
  $media = File::create([
    'uuid' => $field_image_config->getSetting('default_image')['uuid'],
    'uri' => $uri,
    'status' => FileInterface::STATUS_PERMANENT,
  ]);
  $media->save();

  // Ensure Drupal tracks whether the file is still needed.
  \Drupal::service('file.usage')->add($media, 'image', 'default_image', $field_image_config_uuid);
}

/**
 * Implements hook_uninstall().
 */
function social_profile_uninstall() {
  \Drupal::service('config.factory')
    ->getEditable('social_profile.settings')
    ->delete();
}

/**
 * Implements hook_update_last_removed().
 */
function social_profile_update_last_removed() : int {
  // Removed all update hooks before 10.0.
  return 8803;
}

/**
 * Implements hook_update_dependencies().
 */
function social_profile_update_dependencies() {
  // Run the config update after the update_helper module is enabled.
  $dependencies['social_profile'][8808] = [
    'social_core' => 8805,
  ];
  // Run the update hook with the "Grant Profile permissions for the
  // Authenticated user role" only after an update hook "Reassign permissions
  // between roles" in social_core.
  $dependencies['social_profile'][11001] = [
    'social_core' => 11002,
  ];

  // Add variationcache hook update from group as required to fix update path.
  $dependencies['social_profile'][11002] = [
    'group' => 8020,
  ];

  return $dependencies;
}

/**
 * Add a new block to display filters (social_tagging) on all-members page.
 *
 * Load in a config file from an specific update hook that will never change.
 */
function social_profile_update_8901() {
  $config_files = [
    'block.block.exposedformnewest_userspage_newest_users' => \Drupal::service('extension.list.module')->getPath('social_profile') . '/config/static/block.block.exposedformnewest_userspage_newest_users_8901.yml',
    'block.block.socialblue_exposedformnewest_userspage_newest_users' => \Drupal::service('extension.list.module')->getPath('social_profile') . '/config/static/block.block.socialblue_exposedformnewest_userspage_newest_users_8901.yml',
  ];

  foreach ($config_files as $key => $config_file) {
    if (is_file($config_file)) {
      $settings = Yaml::parse(file_get_contents($config_file));
      if (is_array($settings)) {
        $config = \Drupal::configFactory()
          ->getEditable($key);
        $config->setData($settings)->save(TRUE);
      }
    }
  }
}

/**
 * Enable exposed filters block on all-members views.
 */
function social_profile_update_8902() {
  /** @var \Drupal\update_helper\Updater $updateHelper */
  $updateHelper = \Drupal::service('update_helper.updater');

  // Execute configuration update definitions with logging of success.
  $updateHelper->executeUpdate('social_profile', 'social_profile_update_8902');

  // Output logged messages to related channel of update execution.
  return $updateHelper->logger()->output();
}

/**
 * Sets default profile settings.
 */
function social_profile_update_8903() {
  \Drupal::configFactory()
    ->getEditable('social_profile.settings')
    ->set('enable_profile_tagging', TRUE)
    ->save();
}

/**
 * Install Profile name field to profile entity type.
 */
function social_profile_update_8904() {
  $field_storage_definition = BaseFieldDefinition::create('string')
    ->setLabel(t('Profile name'))
    ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED)
    ->setReadOnly(TRUE);

  $update_manager = \Drupal::entityDefinitionUpdateManager();
  $update_manager->installFieldStorageDefinition('profile_name', 'profile', 'social_profile', $field_storage_definition);
}

/**
 * Update field definitions.
 */
function social_profile_update_8905() {
  // Removed in Drupal 9.
}

/**
 * Update profile form labels.
 */
function social_profile_update_10201() {
  /** @var \Drupal\update_helper\Updater $updateHelper */
  $updateHelper = \Drupal::service('update_helper.updater');

  // Execute configuration update definitions with logging of success.
  $updateHelper->executeUpdate('social_profile', 'social_profile_update_10201');

  // Output logged messages to related channel of update execution.
  return $updateHelper->logger()->output();
}

/**
 * Update the configs in the database in accordance with the newest schema.
 */
function social_profile_update_10301() {
  /** @var \Drupal\update_helper\Updater $updateHelper */
  $updateHelper = \Drupal::service('update_helper.updater');

  // Execute configuration update definitions with logging of success.
  $updateHelper->executeUpdate('social_profile', 'social_profile_update_10301');

  // Output logged messages to related channel of update execution.
  return $updateHelper->logger()->output();
}

/**
 * Grant Profile permissions for the Authenticated user role.
 */
function social_profile_update_11001(): void {
  // Authenticated user role permissions.
  user_role_grant_permissions(
    RoleInterface::AUTHENTICATED_ID,
    [
      'add own profile profile',
      'update own profile profile',
      'view own profile profile',
      'view profile',
    ]
  );
}

/**
 * Add roles filter to the Newest users views.
 */
function social_profile_update_11002(): string {
  /** @var \Drupal\update_helper\Updater $updateHelper */
  $updateHelper = \Drupal::service('update_helper.updater');

  // Execute configuration update definitions with logging of success.
  $updateHelper->executeUpdate('social_profile', __FUNCTION__);

  // Output logged messages to related channel of update execution.
  return $updateHelper->logger()->output();
}

/**
 * Empty hook update.
 *
 * @see https://github.com/goalgorilla/open_social/pull/2783
 */
function social_profile_update_11003(array &$sandbox): void {
  // This update hook was a part of release in Open Social 11.0.3.
  // @see https://github.com/goalgorilla/open_social/blob/11.0.3/modules/social_features/social_profile/social_profile.install#L557
  // But this update hook accidentally had hook number 11006 in
  // Open Social 11.1.0-rc1, due to an accidental commit.
  // @see https://github.com/goalgorilla/open_social/blob/11.1.0-rc1/modules/social_features/social_profile/social_profile.install#L557
  // So, we removed code in update hook 11003 in favor of
  // social_profile_update_11007().
  // This is the reason why we don't have update hook number 11004 and 11005.
}

/**
 * Empty hook update.
 *
 * @see https://github.com/goalgorilla/open_social/pull/2783
 */
function social_profile_update_11006(array &$sandbox): void {
  // We moved code to social_profile_update_11007().
  // We had to rewrite this code in order to use 'static' or unchangeable
  // representation of config file instead of file from config/install which
  // could be changed in the future.
}

/**
 * Install 'name' view mode on profile.
 *
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function social_profile_update_11007(array &$sandbox): void {
  $module_path = \Drupal::service('extension.list.module')->getPath('social_profile');
  $view_mode = EntityViewMode::load('profile.name');

  // Check if the view mode exists, so we don't try to create it again.
  if ($view_mode) {
    return;
  }
  $content = file_get_contents($module_path . '/config/static/core.entity_view_mode.profile.name_11007.yml');
  if (is_string($content)) {
    $yml = Yaml::parse($content);
    try {
      EntityViewMode::create($yml)->save();
      $view_display = EntityViewDisplay::load('profile.profile.name');

      // Check if the view display exists, so we don't try to create it again.
      if ($view_display instanceof EntityInterface) {
        return;
      }
      $content = file_get_contents($module_path . '/config/static/core.entity_view_display.profile.profile.name_11007.yml');
      if (is_string($content)) {
        $yml = Yaml::parse($content);
        EntityViewDisplay::create($yml)->save();
      }
    }
    catch (EntityStorageException $e) {
      \Drupal::logger('social_profile')->error($e->getMessage());
    }
  }
}

/**
 * Add a field for a short description.
 */
function social_profile_update_11201(array &$sandbox): string {
  $entity_type_ids = [
    'field.storage.profile.field_profile_' => 'field_storage_config',
    'field.field.profile.profile.field_profile_' => 'field_config',
  ];

  $path = \Drupal::service('extension.list.module')->getPath('social_profile') . '/config/static/';

  foreach ($entity_type_ids as $prefix => $entity_type_id) {
    $config_file = $path . $prefix . 'summary_11201.yml';
    $settings = Yaml::parseFile($config_file);

    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $storage */
    $storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);

    $storage->createFromStorageRecord($settings)->save();
  }

  /** @var \Drupal\update_helper\UpdaterInterface $update_helper */
  $update_helper = \Drupal::service('update_helper.updater');

  // Execute configuration update definitions with logging of success.
  $update_helper->executeUpdate('social_profile', __FUNCTION__);

  // Output logged messages to related channel of update execution.
  return $update_helper->logger()->output();
}

/**
 * Make the user picture clickable and rounded in a compact profile.
 */
function social_profile_update_11202(): string {
  /** @var \Drupal\update_helper\UpdaterInterface $update_helper */
  $update_helper = \Drupal::service('update_helper.updater');

  // Execute configuration update definitions with logging of success.
  $update_helper->executeUpdate('social_profile', __FUNCTION__);

  // Output logged messages to related channel of update execution.
  return $update_helper->logger()->output();
}

/**
 * Make profile related vocabularies translatable.
 */
function social_profile_update_11203(): void {
  if (!\Drupal::moduleHandler()->moduleExists('social_content_translation')) {
    return;
  }

  $config_storage = \Drupal::service('config.storage');
  $config_path = \Drupal::service('extension.list.module')->getPath('social_profile') . '/config/static';
  $source = new FileStorage($config_path);

  $config_names = [
    'language.content_settings.taxonomy_term.expertise' => 'language.content_settings.taxonomy_term.expertise_11203',
    'language.content_settings.taxonomy_term.interests' => 'language.content_settings.taxonomy_term.interests_11203',
    'language.content_settings.taxonomy_term.profile_tag' => 'language.content_settings.taxonomy_term.profile_tag_11203',
  ];

  foreach ($config_names as $name => $static) {
    $config_storage->write($name, (array) $source->read($static));
  }
}

/**
 * Add "Summary" and "Self introduction" fields to the profile teaser.
 */
function social_profile_update_11301(array &$sandbox): string {
  /** @var \Drupal\update_helper\UpdaterInterface $update_helper */
  $update_helper = \Drupal::service('update_helper.updater');

  // Execute configuration update definitions with logging of success.
  $update_helper->executeUpdate('social_profile', __FUNCTION__);

  // Output logged messages to related channel of update execution.
  return $update_helper->logger()->output();
}

/**
 * Update exposed filter button name.
 */
function social_profile_update_11401(): string {
  /** @var \Drupal\update_helper\Updater $updateHelper */
  $updateHelper = \Drupal::service('update_helper.updater');

  // Execute configuration update definitions with logging of success.
  $updateHelper->executeUpdate('social_profile', __FUNCTION__);

  // Output logged messages to related channel of update execution.
  return $updateHelper->logger()->output();
}

/**
 * Fix missed filter block on the all-members page.
 */
function social_profile_update_11701(): string {
  /** @var \Drupal\update_helper\Updater $updateHelper */
  $updateHelper = \Drupal::service('update_helper.updater');

  // Execute configuration update definitions with logging of success.
  $updateHelper->executeUpdate('social_profile', __FUNCTION__);

  // Output logged messages to related channel of update execution.
  return $updateHelper->logger()->output();
}

/**
 * Remove deprecated permissions.
 *
 * Deprecated permissions:
 * - "add own profile profile",
 * - "view profile",
 * - "add any profile profile".
 */
function social_profile_update_111101(): void {
  $entity_type_manager = \Drupal::entityTypeManager();
  /** @var \Drupal\user\RoleInterface[] $roles */
  $roles = $entity_type_manager->getStorage('user_role')->loadMultiple();

  $deprecated_permissions = [
    'add own profile profile',
    'view profile',
    'add any profile profile',
  ];

  foreach ($deprecated_permissions as $deprecated_permission) {
    foreach ($roles as $role) {
      if ($role->hasPermission($deprecated_permission)) {
        $role->revokePermission($deprecated_permission);
        $role->save();
      }
    }
  }
}

/**
 * Add new permissions for taxonomies.
 */
function social_profile_update_121001(): void {
  // Nothing to do if the module is not installed.
  if (!\Drupal::moduleHandler()->moduleExists("taxonomy_access_fix")) {
    return;
  }

  // Permission to grant.
  $taxonomy_permissions = [
    'view terms in interests',
    'view term names in interests',
    'select terms in interests',
    'view terms in expertise',
    'view term names in expertise',
    'select terms in expertise',
    'view terms in profile_tag',
    'view term names in profile_tag',
    'select terms in profile_tag',
  ];

  // Authenticated roles.
  $authenticated_roles = [
    'sitemanager',
    'verified',
    'contentmanager',
  ];

  // Load the permission.
  $entity_type_manager = \Drupal::entityTypeManager();
  foreach ($authenticated_roles as $role) {
    /** @var \Drupal\user\RoleInterface $role */
    $role = $entity_type_manager->getStorage('user_role')->load($role);

    // If the role is not have the permission, grant permission.
    foreach ($taxonomy_permissions as $taxonomy_permission) {
      if (!$role->hasPermission($taxonomy_permission)) {
        $role->grantPermission($taxonomy_permission);
        $role->save();
      }
    }
  }

  // For anonymous role, we need to check the if Registration Fields is enabled.
  if (!\Drupal::moduleHandler()->moduleExists("social_registration_fields")) {
    return;
  }

  // Load anonymous role.
  /** @var \Drupal\user\RoleInterface $role */
  $role = $entity_type_manager->getStorage('user_role')->load('anonymous');

  // If the role is not have the permission, grant permission.
  foreach ($taxonomy_permissions as $taxonomy_permission) {
    if (!$role->hasPermission($taxonomy_permission)) {
      $role->grantPermission($taxonomy_permission);
      $role->save();
    }
  }
}
