eva-8.x-2.1/eva.module
eva.module
<?php /** * @file * Module implementing EVA extra field and views display. */ use Drupal\views\ViewEntityInterface; use Drupal\views\Views; use Drupal\views\Element\View; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Component\Utility\Xss; use Drupal\Component\Utility\Html; use Drupal\Core\Routing\RouteMatchInterface; /** * Implements hook_help(). */ function eva_help($route_name, RouteMatchInterface $route_match) { switch ($route_name) { // Main module help for the eva module. case 'help.page.eva': $output = ''; $output .= '<h3>' . t('About') . '</h3>'; $output .= '<p>' . t('"Eva" is short for "Entity Views Attachment;" it provides a Views display plugin that allows the output of a View to be attached to the content of any Drupal entity.') . '</p>'; return $output; } } /** * Implements hook_entity_extra_field_info(). */ function eva_entity_extra_field_info() { $extra = []; $views = \Drupal::service('eva.view_displays')->get(); foreach ($views as $entity => $data) { foreach ($data as $view) { // If no bundles are configured, apply to all bundles. $bundles = !empty($view['bundles']) ? $view['bundles'] : array_keys(\Drupal::service('entity_type.bundle.info')->getBundleInfo($entity)); foreach ($bundles as $bundle) { $extra[$entity][$bundle]['display'][$view['name'] . '_' . $view['display']] = [ 'label' => (empty($view['title'])) ? $view['name'] : $view['title'], 'description' => $view['title'], 'weight' => 10, ]; // Provide a separate extra field for the exposed form if there is any. if ($view['uses exposed']) { $extra[$entity][$bundle]['display'][$view['name'] . '_' . $view['display'] . '_form'] = [ 'label' => ((empty($view['title'])) ? $view['name'] : $view['title']) . ' (' . t('Exposed form') . ')', 'description' => t('The exposed filter form of the view.'), 'weight' => 9, ]; } } } } return $extra; } /** * Implements hook_entity_view(). */ function eva_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) { $type = $entity->getEntityTypeId(); $views = \Drupal::service('eva.view_displays')->get($type); $token_handler = \Drupal::service('eva.token_handler'); foreach ($views as $info) { $longname = $info['name'] . '_' . $info['display']; if ($display->getComponent($longname)) { if ($view = Views::getView($info['name'])) { $view->setDisplay($info['display']); if ((empty($info['bundles']) || in_array($display->getTargetBundle(), $info['bundles'])) && $view->access($info['display'])) { // Gather info about the attached-to entity. $entity_type = $view->display_handler->getOption('entity_type'); $arg_mode = $view->display_handler->getOption('argument_mode'); if ($arg_mode == 'token') { if ($token_string = $view->display_handler->getOption('default_argument')) { // Now do the token replacement. $token_values = $token_handler->getArgumentsFromTokenString($token_string, $entity_type, $entity); $new_args = []; // We have to be careful to only replace arguments that have // tokens. foreach ($token_values as $key => $value) { $new_args[Html::escape($key)] = Html::escape($value); } $view->args = $new_args; } } elseif ($arg_mode == 'id') { $view->args = [$entity->id()]; } // Add an argument cache key // If there are more than one of the same Eva on the same page, // the first one gets cached. // Presumably they should vary by contextual argument, so this // adds a cache key for the argument(s). // see https://www.drupal.org/node/2873385 if ($view->args) { $view->element['#cache'] += ['keys' => []]; $view->element['#cache']['keys'] = array_merge([implode(':', $view->args)], $view->element['#cache']['keys']); } // Now that arguments are set, build the exposed form. if ($info['uses exposed'] && $display->getComponent($longname . '_form')) { $view->initHandlers(); /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form */ $exposed_form = $view->display_handler->getPlugin('exposed_form'); $build[$longname . '_form'] = $exposed_form->renderExposedForm(TRUE); } // Build the render. if ($output = $view->buildRenderable($info['display'])) { // EVA module expects to get a final render array, without another // top-level #pre_render callback. So, here we make sure that Views' // #pre_render callback has already been applied. // // Also needed for view_build to be present. // // @see \Drupal\views\Plugin\Block\ViewsBlock $output = View::preRenderViewElement($output); // When view_build is empty, the actual render array output for this View // is going to be empty. In that case, return just #cache, so that the // render system knows the reasons (cache contexts & tags) why this Views // EVA is empty, and can cache it accordingly. // // @see \Drupal\views\Plugin\Block\ViewsBlock if (empty($output['view_build'])) { $output = ['#cache' => $output['#cache']]; } $build[$longname] = $output; } } } } } } /** * Implements hook_modules_enabled(). */ function eva_modules_enabled($modules) { \Drupal::service('eva.view_displays')->reset(); } /** * Implements hook_modules_disabled(). */ function eva_modules_disabled($modules) { \Drupal::service('eva.view_displays')->reset(); } /** * Implements hook_ENTITY_TYPE_presave(). * * Address https://www.drupal.org/node/2922112: if Eva displays are removed, * remove the module dependency from the View. */ function eva_view_presave(ViewEntityInterface $view) { $dependencies = $view->get('dependencies'); if (in_array('eva', $dependencies['module'] ?? [])) { $eva_count = 0; foreach ($view->get('display') as $display) { // Is there a display that's still using Eva? if ($display['display_plugin'] == 'entity_view') { $eva_count += 1; } } // No Eva's? Remove the dependency. if ($eva_count == 0) { $dependencies['module'] = array_values(array_diff($dependencies['module'], ['eva'])); $view->set('dependencies', $dependencies); } } } /** * Implements hook_views_invalidate_cache(). */ function eva_views_invalidate_cache() { $utilities = \Drupal::service('eva.view_displays'); $utilities->clearDetached(); $utilities->invalidateCaches(); // See https://www.drupal.org/node/2281897 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); } /** * Templating preprocessing. * * Figure out the title and whether there's an exposed form. */ function template_preprocess_eva_display_entity_view(&$variables) { $view = $variables['view']; $display = $view->display_handler; $variables['title'] = $display->getOption('show_title') ? Xss::filterAdmin($view->getTitle()) : ''; $variables['exposed_form_as_field'] = $display->getOption('exposed_form_as_field'); $id = $view->storage->id(); $variables['css_name'] = Html::cleanCssIdentifier($id); $variables['id'] = $id; $variables['display_id'] = $view->current_display; $variables['dom_id'] = $view->dom_id; // Pull in the display class. $css_class = $view->display_handler->getOption('css_class'); if (!empty($css_class)) { $variables['css_class'] = preg_replace('/[^a-zA-Z0-9- ]/', '-', $css_class); $variables['attributes']['class'][] = $variables['css_class']; } $variables['view_array']['#view_id'] = $view->storage->id(); $variables['view_array']['#view_display_show_admin_links'] = $view->getShowAdminLinks(); $variables['view_array']['#view_display_plugin_id'] = $display->getPluginId(); views_add_contextual_links($variables['view_array'], 'view', $display->getLinkDisplay()); }