mix-1.1.0-rc1/mix.module

mix.module
<?php

/**
 * @file
 * Primary module hooks for Mix module.
 */

use Drupal\block\BlockInterface;
use Drupal\block\Entity\Block;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Markup;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\image\Entity\ImageStyle;
use Drupal\mix\Controller\Mix;
use Drupal\mix\Controller\MixContentSyncController;
use Drupal\user\Entity\Role;
use Drupal\views\ViewExecutable;
use Symfony\Component\HttpFoundation\RedirectResponse;

/**
 * Implements hook_help().
 */
function mix_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.mix':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Mix is a collection of features for Drupal site building, management, development and user experience improvement. For more information, see the <a href=":url">online project page for the Mix module</a>.', [':url' => 'https://www.drupal.org/project/mix']) . '</p>';
      return $output;
  }
}

/**
 * Implements hook_form_alter().
 */
function mix_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // Hide submit button.
  if (\Drupal::config('mix.settings')->get('hide_submit')) {
    // Attach hide submit button library to form.
    $form['#attached']['library'][] = 'mix/hide_submit';
  }

  // Unsaved form confirm.
  if (\Drupal::config('mix.settings')->get('unsaved_form_confirm')) {
    // Attach library to form.
    $form['#attached']['library'][] = 'mix/unsaved_form_confirm';
  }

  // Show form id.
  if (\Drupal::state()->get('mix.show_form_id')) {
    $form['mix_show_form_id'] = [
      '#type' => 'inline_template',
      '#template' => '<div class="mix-box mix-warning mix-full-width">
Form ID: <code>{{ form_id }}</code><br>
Copy/paste following template to [yourmodule].module file to alter this form (Replace <code>hook_</code> with <code>[yourmodule]_</code>): <br>
<textarea class="mix-code" rows="6" style="width: 100%; min-width: 280px;">
/**
 * Implements hook_form_FORM_ID_alter().
 */
function hook_form_{{ form_id }}_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
}
</textarea><br>
@see <a href="https://api.drupal.org/hook_form_FORM_ID_alter" target="_blank">hook_form_FORM_ID_alter()</a>
</div>',
      '#context' => [
        'form_id' => $form_id,
      ],
      '#weight' => -1000,
    ];

    $form['mix_show_form_id']['#attached']['library'][] = 'mix/preset';
  }

  // Hide revision field.
  $currentUser = \Drupal::currentUser();
  $hideRevisionField = \Drupal::config('mix.settings')->get('hide_revision_field');
  if ($currentUser->id() != 1 && $hideRevisionField) {
    $form['revision_information']['#access'] = FALSE;
  }

  // Ajax forms.
  // @see AjaxHelperTrait::isAjax()
  $isAjax = in_array(\Drupal::request()->get('_wrapper_format'), [
    'drupal_ajax',
    'drupal_dialog',
    'drupal_modal',
  ]);

  if ($isAjax && isset($form['#mix_ajax_form']) && $form['#mix_ajax_form']) {
    $wrapper_id = "mix_ajax_form_" . $form_id;
    $form['#prefix'] = '<div id="' . $wrapper_id . '">';
    $form['#suffix'] = '</div>';
    $form['actions']['submit']['#ajax'] = [
      'callback' => '\Drupal\mix\Controller\Mix::ajaxFormSubmit',
      'wrapper' => $wrapper_id,
      'effect' => 'fade',
    ];
  }

}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function mix_form_user_admin_settings_alter(&$form, FormStateInterface $form_state, $form_id) {
  $config = \Drupal::config('mix.settings');
  $form['registration_cancellation']['mix_register_password'] = [
    '#type' => 'checkbox',
    '#title' => t('Show password fields on registration form.'),
    '#description' => t('Provided by <a href=":url" target="_blank">Mix</a> module', [':url' => 'https://www.drupal.org/project/mix']),
    '#default_value' => $config->get('register_password'),
  ];
  $form['#submit'][] = '_mix_user_admin_settings_submit';
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function mix_form_user_register_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  $registerWithPassword = \Drupal::config('mix.settings')->get('register_password');

  // If 'register with password' is enabled and there is no 'password' field,
  // add password fields and submit function in register form.
  if ($registerWithPassword && !isset($form['account']['pass'])) {
    $form['account']['mix_register_password'] = [
      '#type' => 'password_confirm',
      '#size' => 25,
      '#description' => t('Provide a password for the new account in both fields.'),
      '#required' => TRUE,
    ];
    // Add submit function right before ::save() to override random password
    // by the value of the 'mix_register_password' field.
    // @see RegisterForm::save()
    $index = array_search('::save', $form['actions']['submit']['#submit']);
    array_splice($form['actions']['submit']['#submit'], $index, 0, '_mix_form_user_register_submit');
  }
}

/**
 * Save user admin settings.
 */
function _mix_user_admin_settings_submit(&$form, FormStateInterface $form_state) {
  $config = \Drupal::configFactory()->getEditable('mix.settings');
  $config->set('register_password', $form_state->getValue('mix_register_password'))
    ->save();
}

/**
 * Save user password with the value of the 'mix_register_password' field.
 */
function _mix_form_user_register_submit(&$form, FormStateInterface $form_state) {
  $pass = $form_state->getValue('mix_register_password');
  $form_state->setValue('pass', $pass);
  $account = $form_state->getFormObject()->getEntity();
  $account->setPassword($pass);
}

/**
 * Implements hook_page_top().
 */
function mix_page_top(array &$page_top) {

  $text = \Drupal::state()->get('mix.environment_indicator');

  if ($text) {

    // Add an edit link if user has permission.
    $editLink = '';
    if (\Drupal::currentUser()->hasPermission('administer site configuration')) {
      $url = new Url('mix.settings', [], ['fragment' => 'edit-environment-indicator']);
      $editLink = \Drupal::linkGenerator()->generate(t('Edit'), $url);
    }

    $page_top['mix_environment_indicator'] = [
      '#type' => 'inline_template',
      '#template' => '<div id="mix-environment-indicator" style="color: #fff; background: orange; text-align: center;">{{ text }} {{ edit_link }}</div>',
      '#context' => [
        'text' => $text,
        'edit_link' => $editLink,
      ],
      '#cache' => [
        'tags' => [
          'mix:environment-indicator',
        ],
      ],
    ];
  }

}

/**
 * Remove the "Generator" meta tag from the <head> section.
 */
function mix_page_attachments_alter(array &$attachments): void {
  if (\Drupal::config('mix.settings')->get('remove_x_generator')) {
    foreach ($attachments['#attached']['html_head'] as $key => $attachment) {
      if ($attachment[1] == 'system_meta_generator') {
        unset($attachments['#attached']['html_head'][$key]);
      }
    }
  }
}

/**
 * Implements template_preprocess_html().
 */
function mix_preprocess_html(&$variables) {

  // Add meta tags and change page title based on the meta settings.
  $metaConfig = \Drupal::config('mix.settings')->get('meta');
  $token_service = \Drupal::token();

  // Meta tags for front page.
  $is_front = \Drupal::service('path.matcher')->isFrontPage();
  if ($is_front && $metaConfig['frontpage']['active']) {
    // Set page title.
    if ($metaConfig['frontpage']['title']) {
      $frontpage_title = $metaConfig['frontpage']['title'];
      // Replace token.
      if ($token_service->scan($frontpage_title)) {
        $frontpage_title = $token_service->replace($frontpage_title);
      }
      $variables['head_title'] = html_entity_decode($frontpage_title, ENT_QUOTES);
    }

    // Add pre-defined meta tags.
    $meta_names = ['description', 'keywords'];
    foreach ($meta_names as $name) {
      if ($metaConfig['frontpage']['description']) {
        $meta = [
          '#tag' => 'meta',
          '#attributes' => [
            'name' => $name,
            'content' => $metaConfig['frontpage'][$name],
          ],
        ];
        $variables['#attached']['html_head'][] = [$meta, $name];
      }
    }

    // Add user-defined meta tags.
    if ($metaConfig['frontpage']['metatags']) {
      // Parse metatags.
      $meta_tags_array = Mix::getMetaTags($metaConfig['frontpage']['metatags']);
      foreach ($meta_tags_array as $m) {
        $meta = [
          '#tag' => 'meta',
          '#attributes' => [
            $m['attribute'] => $m['value'],
            'content' => $m['content'],
          ],
        ];
        $name = $m['attribute'] . '-' . $m['value'];
        $variables['#attached']['html_head'][] = [$meta, $name];
      }
    }

  }

  // Node.
  // Add meta tags for full node pages.
  $route_match = \Drupal::routeMatch();
  if ($route_match->getRouteName() == 'entity.node.canonical' && $metaConfig['node']['active']) {

    if ($metaConfig['node']['description']) {
      $content = $metaConfig['node']['description'];

      // Replace token as needed.
      if ($token_service->scan($content)) {
        $node = $route_match->getParameter('node');
        if ($node) {
          $content = Xss::filter($token_service->replace($content, ['node' => $node]));
        }
      }

      // Add tags.
      $meta = [
        '#tag' => 'meta',
        '#attributes' => [
          'name' => 'description',
          'content' => $content,
        ],
      ];
      $variables['#attached']['html_head'][] = [$meta, 'description'];
    }
  }

}

/**
 * Implements hook_perprocess_views_view_field().
 */
function mix_preprocess_views_view_field(&$variables) {
  $view = $variables['view'];
  $field = $variables['field'];

  if (\Drupal::config('mix.settings')->get('show_content_sync_id')) {
    // Add content sync widget after block title.
    // @see mix_views_pre_render()
    if ($view->id() == 'block_content' && $view->current_display == 'page_1' && $field->options['id'] == 'info') {
      $markup = $variables['output'] . ' ' . MixContentSyncController::getWidget($variables['row']->content_sync_id);
      $variables['output'] = ['#markup' => $markup];
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function mix_form_user_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Hide password field if 'standalone_password_page' is enabled.
  $mixConfig = \Drupal::config('mix.settings');
  if ($mixConfig->get('standalone_password_page')) {
    // Hide password fields.
    if (isset($form['account']['pass'])) {
      $form['account']['pass']['#access'] = FALSE;
    }
    // Redirect user to standalone password page if 'pass-reset-token' in url.
    $token = \Drupal::request()->get('pass-reset-token');
    if ($token) {
      $options = ['query' => ['pass-reset-token' => $token]];
      // Return a redirect response.
      $account = $form_state->getFormObject()->getEntity();
      $url = Url::fromRoute('mix.change_password_form', ['user' => $account->id()], $options)->toString();
      $redirectResponse = new RedirectResponse($url);
      $redirectResponse->send();
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function mix_form_block_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  $form_object = $form_state->getFormObject();
  $block = $form_object->getEntity();

  // Add CSS class(es) field to all block.
  // This will automatically be saved in the third party settings.
  $form['third_party_settings']['#tree'] = TRUE;
  $form['third_party_settings']['mix']['class'] = [
    '#type' => 'textfield',
    '#title' => t('CSS class(es)'),
    '#description' => t('Add custom CSS classes to the block wrapper, make it easy to style/manuplate by CSS/JS. Use space to separate multiple classes.') . '<br>' .
    t('Provided by <a href=":url" target="_blank">Mix</a> module', [':url' => 'https://www.drupal.org/project/mix']),
    '#default_value' => $block->getThirdPartySetting('mix', 'class'),
    '#maxlength' => 255,
  ];

  $pluginId = $block->getPluginId();

  // Add a dropdown checkbox for menu blocks.
  if (strpos($pluginId, 'system_menu_block:') === 0) {
    $form['third_party_settings']['mix']['dropdown'] = [
      '#title' => t('Display as a dropdown menu.'),
      '#description' => t('Provided by <a href=":url" target="_blank">Mix</a> module', [':url' => 'https://www.drupal.org/project/mix']),
      '#type' => 'checkbox',
      '#default_value' => $block->getThirdPartySetting('mix', 'dropdown'),
    ];
  }
}

/**
 * Implements hook_views_pre_render().
 */
function mix_views_pre_render(ViewExecutable $view) {

  if (\Drupal::config('mix.settings')->get('show_content_sync_id')) {
    // Add the content sync ID to the "Custom block library" view.
    if ($view->id() == 'block_content' && $view->current_display == 'page_1') {
      foreach ($view->result as $key => $row) {
        $entity = $row->_entity;
        $content_sync_id = 'mix.content_sync.' . $entity->getEntityTypeId() . '.' . $entity->bundle() . '.' . $entity->uuid();
        $view->result[$key]->content_sync_id = $content_sync_id;
      }
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function mix_block_presave(BlockInterface $entity) {
  // @see Drupal\Core\Config\Entity\ThirdPartySettingsInterface
  if (empty($entity->getThirdPartySetting('mix', 'class'))) {
    $entity->unsetThirdPartySetting('mix', 'class');
  }

  if (empty($entity->getThirdPartySetting('mix', 'dropdown'))) {
    $entity->unsetThirdPartySetting('mix', 'dropdown');
  }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function mix_preprocess_block(&$variables) {
  if (!empty($variables['elements']['#id'])) {
    $block = Block::load($variables['elements']['#id']);

    if (!$block) {
      return;
    }

    // Add custom CSS class to the block.
    if ($classes = $block->getThirdPartySetting('mix', 'class')) {
      $classes = explode(' ', $classes);
      foreach ($classes as $class) {
        $variables['attributes']['class'][] = $class;
      }
    }

    // Add dropdown feature to the menu block.
    if ($block->getThirdPartySetting('mix', 'dropdown')) {
      $variables['#attached']['library'][] = 'mix/dropdown';
      // Add class to the block.
      $variables['attributes']['class'][] = 'mix-dropdown-box';
      // Add class to the menu, e.g. <ul>.
      $variables['content']['#attributes']['class'][] = 'mix-dropdown';
      // Rplace #theme to use custom template.
      // @see mix_theme_registry_alter().
      $variables['content']['#theme'] = 'mix_menu';
    }
  }

}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function mix_form_menu_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // Add the content sync ID to the menu edit form.
  if (\Drupal::config('mix.settings')->get('show_content_sync_id')) {
    foreach ($form['links']['links'] as $key => $link) {
      if (strpos($key, 'menu_plugin_id:menu_link_content:') === 0) {
        if (isset($link['title'][1])) {
          $content_sync_id = 'mix.content_sync.' . str_replace(':', '.', $link['id']['#value']);
          $form['links']['links'][$key]['title'][1]['#suffix'] = ' ' . MixContentSyncController::getWidget($content_sync_id);
        }
      }
    }
  }

}

/**
 * Implements hook_theme_registry_alter().
 */
function mix_theme_registry_alter(&$theme_registry) {

  // Copy core menu theme settings to keep original behaviors.
  $theme_registry['mix_menu'] = $theme_registry['menu'];

  // Override template path to use custom template.
  // @see mix_preprocess_block().
  $modulePath = \Drupal::moduleHandler()->getModule('mix')->getPath();
  $theme_registry['mix_menu']['theme path'] = $modulePath;
  $theme_registry['mix_menu']['path'] = $modulePath . '/templates';

}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function mix_form_menu_link_edit_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Add advanced settings to pre-defined menu links.
  mix_form_menu_link_content_form_alter($form, $form_state, $form_id);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function mix_form_menu_link_content_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // menu_link_edit - for pre-defined menu links.
  if ($form_id == 'menu_link_edit') {
    $menu_link_options = \Drupal::state()->get('mix.menu_settings.' . $form['menu_link_id']['#value']);
    // Add custom submit callback.
    $form['actions']['submit']['#submit'][] = 'mix_menu_link_edit_submit';
  }
  // menu_link_content_menu_link_content_form - for menu_link_content.
  elseif ($form_id == 'menu_link_content_menu_link_content_form') {

    // Load link options.
    $menu_link = $form_state->getFormObject()->getEntity();
    $menu_link_options = $menu_link->link->options;

    $form['mix_menu_token'] = [
      '#title' => t('Menu token'),
      '#type' => 'checkbox',
      '#description' => t('Allow to use tokens ([current-user:xxx], [site:xxx]) in Menu link title and Link. See the <a href=":url">online documentation</a> for avaiable tokens.', [':url' => 'https://www.drupal.org/docs/extending-drupal/contributed-modules/contributed-module-documentation/mix/menu-token']) . '<br>'
      . t('Provided by Mix module.'),
      '#default_value' => TRUE,
      '#disabled' => TRUE,
      // Show this just under menu title field.
      // See MenuLinkContent::baseFieldDefinitions().
      '#weight' => -4,
    ];

    $form['mix_allow_html'] = [
      '#type' => 'checkbox',
      '#title' => t('Allow HTML'),
      '#description' => t('Allow to use HTML in Menu link title.') . '<br>' . t('Provided by Mix module.'),
      '#default_value' => $menu_link_options['mix']['allow_html'] ?? FALSE,
      '#weight' => -4,
    ];

    // Add an additional function to save options.
    $form['#entity_builders']['mix'] = 'mix_menu_link_content_form_entity_builder';
  }

  if (isset($menu_link_options['attributes']['class']) && is_array($menu_link_options['attributes']['class'])) {
    $menu_link_options['attributes']['class'] = implode(' ', $menu_link_options['attributes']['class']);
  }

  $form['mix_advanced'] = [
    '#type' => 'details',
    '#title' => t('Advanced (by Mix module)'),
    '#tree' => TRUE,
    '#open' => TRUE,
  ];

  // Roles.
  $roles = Role::loadMultiple();
  $options = [];
  foreach ($roles as $key => $role) {
    $options[$key] = $role->get('label');
  }
  $form['mix_advanced']['roles'] = [
    '#type' => 'checkboxes',
    '#title' => t('The roles that can see this menu item.'),
    '#options' => $options,
    '#description' => t('To allow all roles to see this link, leave it empty.'),
    '#default_value' => $menu_link_options['mix']['roles'] ?? [],
  ];

  // Link attributes.
  $form['mix_advanced']['attributes'] = [
    '#type' => 'details',
    '#title' => t('Link attributes'),
    '#description' => t('Set the link target to open it in a new window, or add id and class so that JS/CSS can easily interact with the link.'),
    '#open' => TRUE,
  ];

  $form['mix_advanced']['attributes']['id'] = [
    '#type' => 'textfield',
    '#title' => t('id'),
    '#description' => t('Add an id attribute to this link'),
    '#default_value' => $menu_link_options['attributes']['id'] ?? '',
  ];

  $form['mix_advanced']['attributes']['class'] = [
    '#type' => 'textfield',
    '#title' => t('class'),
    '#description' => t('Add CSS class(es) to this link, seperated by a blank.'),
    '#default_value' => $menu_link_options['attributes']['class'] ?? '',
  ];

  $form['mix_advanced']['attributes']['target'] = [
    '#type' => 'textfield',
    '#title' => t('target'),
    '#description' => t('Add a target attribute to this link, e.g. "_blank", "_self"'),
    '#default_value' => $menu_link_options['attributes']['target'] ?? '',
  ];

  // Link container attributes.
  $form['mix_advanced']['container_attributes'] = [
    '#type' => 'details',
    '#title' => t('Link container attributes'),
    '#description' => t('Set attributes to the container element (<code>&lt;li&gt;</code>) of the menu link.'),
  ];

  $form['mix_advanced']['container_attributes']['id'] = [
    '#type' => 'textfield',
    '#title' => t('id'),
    '#description' => t('Add an id attribute to the link container'),
    '#default_value' => $menu_link_options['mix']['container_attributes']['id'] ?? '',
  ];

  $form['mix_advanced']['container_attributes']['class'] = [
    '#type' => 'textfield',
    '#title' => t('class'),
    '#description' => t('Add CSS class(es) to the link container, seperate by a blank.'),
    '#default_value' => $menu_link_options['mix']['container_attributes']['class'] ?? '',
  ];

  // Add an addition submit function to handle menu token.
  $form['actions']['submit']['#submit'][] = 'mix_menu_token_form_submit';
}

/**
 * Additional submit function to handle menu token.
 */
function mix_menu_token_form_submit(array &$form, FormStateInterface $form_state) {
  // Rebuld menu link after form submit to avoid 'no corresponding route' error.
  \Drupal::service('plugin.manager.menu.link')->rebuild();
}

/**
 * Build menu link options from $form_state.
 */
function _mix_build_menu_link_options(array &$form, FormStateInterface $form_state) {
  $attributes = $form_state->getValue('mix_advanced')['attributes'];
  // Remove attributes if it's empty.
  foreach ($attributes as $key => $attr) {
    if (!$attr) {
      unset($attributes[$key]);
    }
  }
  // Turn class string into array.
  if (isset($attributes['class']) && !is_array($attributes['class'])) {
    $attributes['class'] = array_filter(explode(' ', $attributes['class']));
  }

  $options = [
    // These attributes will be used in mix_preprocess_menu().
    'mix' => [
      'roles' => $form_state->getValue('mix_advanced')['roles'],
      'container_attributes' => $form_state->getValue('mix_advanced')['container_attributes'],
      'allow_html' => $form_state->getValue('mix_allow_html'),
    ],
    // These attributes will apply to the menu link by core.
    'attributes' => $attributes,
  ];
  return $options;
}

/**
 * Save advanced settings for pre-defined menu links.
 */
function mix_menu_link_edit_submit(array &$form, FormStateInterface $form_state) {

  $options = _mix_build_menu_link_options($form, $form_state);

  // Save menu settings by State API.
  // Can't save it as menu_tree's options, it will be restored
  // after a cache clear.
  \Drupal::state()->set('mix.menu_settings.' . $form['menu_link_id']['#value'], $options);
}

/**
 * Save advanced settings for menu_link_content.
 */
function mix_menu_link_content_form_entity_builder($entity_type, $menu_link, &$form, &$form_state) {

  $options = _mix_build_menu_link_options($form, $form_state);

  // Set link options.
  $menu_link->link->first()->options = $options;
}

/**
 * Implements hook_link_alter().
 */
function mix_link_alter(&$variables) {

  // Check if allow HTML in this link.
  $allowHtml = $variables['options']['mix']['allow_html'] ?? FALSE;

  $token_service = \Drupal::token();

  // Replace menu token in Url.
  if (isset($variables['options']['mix']['menu_token']['url'])) {

    // Support simple tokens like [current-user:xxx], [site:xxx], [date:xxx].
    // which don't need to pass $data manually.
    //
    // @TBD: Support [user:xxx], [node:xxx] and more?
    //
    // Get url from options and replace tokens.
    $tokenUrl = str_replace('base:', '/', $variables['options']['mix']['menu_token']['url']);
    $url = $token_service->replace($tokenUrl);
    // Generate Url object with $url.
    $urlObject = Url::fromUserInput($url, $variables['options']);
    // Use new Url object.
    $variables['url'] = $urlObject;
  }

  // Set $allowHtml to TRUE if menu title contains string prefix of
  // token [current-user:picture] or [current-user:picture:xxx].
  if (isset($variables['options']['mix']['menu_token']['title'])
    && strpos($variables['options']['mix']['menu_token']['title'], '[current-user:picture') !== FALSE) {
    $allowHtml = TRUE;
  }

  // Don't escape HTML characters in menu title if $allowHtml is set to TRUE.
  if ($allowHtml) {
    $variables['text'] = Markup::create($variables['text']);
  }
}

/**
 * Implements template_preprocess_menu().
 */
function mix_preprocess_menu(&$variables) {
  _mix_preprocess_menu_item($variables, $variables['items']);
}

/**
 * Implements hook_menu_links_discovered_alter().
 */
function mix_menu_links_discovered_alter(&$links) {

  $token_service = \Drupal::token();
  foreach ($links as &$link) {

    if (isset($link['title']) && $token_service->scan($link['title'])) {
      $link['options']['mix']['menu_token']['title'] = $link['title'];
    }

    if (isset($link['url']) && $token_service->scan($link['url'])) {
      // Save original link information into options.
      // Then it can be used in _mix_preprocess_menu_item() and other functions.
      $link['options']['mix']['menu_token']['url'] = $link['url'];

      // Replace route_name and url to avoid 'has no corresponding route' error.
      // @see Drupal\Core\Url::getRouteName()
      $link['url'] = NULL;
      $link['route_name'] = '<none>';
    }

  }

}

/**
 * Control menu item visibilities and attributes based on stored options.
 */
function _mix_preprocess_menu_item(&$variables, &$items) {

  $currentUserRoles = \Drupal::currentUser()->getRoles();

  $token_service = \Drupal::token();

  foreach ($items as $id => &$item) {

    // Replace title if it contains token.
    // @see mix_link_alter().
    if ($token_service->scan($item['title'])) {
      $item['title'] = $token_service->replace($item['title']);
    }

    // Set url options.
    if (strpos($id, 'menu_link_content:') === 0) {
      $options = $item['url']->getOption('mix');
    }
    else {
      // @TBD Replace the State API? Use K-V, storage, config or other methods
      // to make deployment easier.
      $advancedSettings = \Drupal::state()->get('mix.menu_settings.' . $id);
      if ($advancedSettings) {
        $item['url']->setOptions($advancedSettings + $item['url']->getOptions());
      }
      $options = $advancedSettings['mix'] ?? [];
    }

    // Hide menu item if $allowedRoles is not empty
    // and current user don't have those roles.
    $allowedRoles = isset($options['roles']) && is_array($options['roles']) ? array_filter($options['roles']) : [];

    if ($allowedRoles) {
      // Add cache context if menu item has access control based on roles.
      if (!isset($variables['#cache']['contexts']) || !array_search('user.roles', $variables['#cache']['contexts'])) {
        $variables['#cache']['contexts'][] = 'user.roles';
      }

      // Hide menu item if current user don't have allowed roles.
      if (!array_intersect($allowedRoles, $currentUserRoles)) {
        unset($items[$id]);
        continue;
      }
    }

    // Set attributes to the menu item container.
    if (isset($options['container_attributes'])) {
      foreach ($options['container_attributes'] as $key => $value) {
        if ($value) {
          $item['attributes']->setAttribute($key, $value);
        }
      }
    }

    if (!empty($item['below'])) {
      _mix_preprocess_menu_item($variables, $item['below']);
    }
  }

}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function mix_form_taxonomy_overview_terms_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Add the content sync ID to the taxonomy_overview_terms form.
  if (\Drupal::config('mix.settings')->get('show_content_sync_id')) {
    foreach ($form['terms'] as $key => $link) {
      if (strpos($key, 'tid:') === 0) {
        $content_sync_id = 'mix.content_sync.taxonomy.term.' . $link['#term']->uuid();
        $form['terms'][$key]['term']['#suffix'] = ' ' . MixContentSyncController::getWidget($content_sync_id);
      }
    }
  }
}

/**
 * Implements hook_token_info().
 */
function mix_token_info() {
  // Add new tokens [current-user:picture] and
  // [current-user:picture:image-style] to display user picture.
  // Change 'picture' to 'mix-picture' if it conflicts with core token
  // in the future.
  $user['picture'] = [
    'name' => t('User picture') . ' ' . t('(Provided by Mix module)'),
  ];

  return [
    'tokens' => ['user' => $user],
  ];
}

/**
 * Implements hook_tokens().
 */
function mix_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {

  $token_service = \Drupal::token();

  $replacements = [];

  if ($type == 'user' && !empty($data['user'])) {
    $user = $data['user'];

    foreach ($tokens as $token_name => $token_str) {

      // Handle token [user:picture].
      if ($token_name == 'picture') {
        $user_picture = mix_get_user_picture($user);
        // Use markup to prevent HTML escaped.
        $markup = Markup::create('<img src="' . $user_picture . '" alt="' . $user->getDisplayName() . '"  />');
        $replacements[$token_str] = $markup;
      }

      // Handle token [user:picture:?].
      if ($sub_tokens = $token_service->findWithPrefix($tokens, 'picture')) {
        foreach ($sub_tokens as $imagestyle_name => $sub_token_str) {
          $user_picture = mix_get_user_picture($user, $imagestyle_name);
          // Use markup to prevent HTML escaped.
          $markup = Markup::create('<img src="' . $user_picture . '" alt="' . $user->getDisplayName() . '"  />');
          $replacements[$sub_token_str] = $markup;
        }
      }

    }

  }

  return $replacements;
}

/**
 * Get the user picture of the given user.
 */
function mix_get_user_picture($account, $imagestyle_name = NULL) {
  // Try user uploaded picture.
  $file = $account->user_picture->entity;

  if (!$file) {
    // Try default picture of user_picture field.
    $field_info = FieldConfig::loadByName('user', 'user', 'user_picture');
    $settings = $field_info->getSetting('default_image');
    if (isset($settings['uuid'])) {
      $file = Drupal::service('entity.repository')->loadEntityByUuid('file', $settings['uuid']);
    }
  }

  // Use drupal logo if no user uploaded picture or a default picture.
  if (!$file) {
    $baseurl = Url::fromRoute('<front>', [], ['absolute' => TRUE])->toString();
    return $baseurl . 'core/misc/logo/drupal-logo.svg';
  }

  // Build picture url if not use the Mix default user picture.
  if ($imagestyle_name && $imagestyle = ImageStyle::load($imagestyle_name)) {
    return $imagestyle->buildUrl($file->getFileUri());
  }
  else {
    return $file->createFileUrl();
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc