search_api-8.x-1.15/src/Plugin/search_api/processor/RenderedItem.php
src/Plugin/search_api/processor/RenderedItem.php
<?php
namespace Drupal\search_api\Plugin\search_api\processor;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\Entity\EntityViewMode;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountSwitcherInterface;
use Drupal\Core\Session\UserSession;
use Drupal\Core\Theme\ThemeInitializationInterface;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\search_api\Datasource\DatasourceInterface;
use Drupal\search_api\Item\ItemInterface;
use Drupal\search_api\LoggerTrait;
use Drupal\search_api\Plugin\search_api\processor\Property\RenderedItemProperty;
use Drupal\search_api\Processor\ProcessorPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Adds an additional field containing the rendered item.
*
* @see \Drupal\search_api\Plugin\search_api\processor\Property\RenderedItemProperty
*
* @SearchApiProcessor(
* id = "rendered_item",
* label = @Translation("Rendered item"),
* description = @Translation("Adds an additional field containing the rendered item as it would look when viewed."),
* stages = {
* "add_properties" = 0,
* },
* locked = true,
* hidden = true,
* )
*/
class RenderedItem extends ProcessorPluginBase {
use LoggerTrait;
/**
* The current_user service used by this plugin.
*
* @var \Drupal\Core\Session\AccountSwitcherInterface|null
*/
protected $accountSwitcher;
/**
* The renderer to use.
*
* @var \Drupal\Core\Render\RendererInterface|null
*/
protected $renderer;
/**
* Theme manager service.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* Theme initialization service.
*
* @var \Drupal\Core\Theme\ThemeInitializationInterface
*/
protected $themeInitialization;
/**
* Theme settings config.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
/** @var static $plugin */
$plugin = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$plugin->setAccountSwitcher($container->get('account_switcher'));
$plugin->setRenderer($container->get('renderer'));
$plugin->setLogger($container->get('logger.channel.search_api'));
$plugin->setThemeManager($container->get('theme.manager'));
$plugin->setThemeInitializer($container->get('theme.initialization'));
$plugin->setConfigFactory($container->get('config.factory'));
return $plugin;
}
/**
* Retrieves the account switcher service.
*
* @return \Drupal\Core\Session\AccountSwitcherInterface
* The account switcher service.
*/
public function getAccountSwitcher() {
return $this->accountSwitcher ?: \Drupal::service('account_switcher');
}
/**
* Sets the account switcher service.
*
* @param \Drupal\Core\Session\AccountSwitcherInterface $current_user
* The account switcher service.
*
* @return $this
*/
public function setAccountSwitcher(AccountSwitcherInterface $current_user) {
$this->accountSwitcher = $current_user;
return $this;
}
/**
* Retrieves the renderer.
*
* @return \Drupal\Core\Render\RendererInterface
* The renderer.
*/
public function getRenderer() {
return $this->renderer ?: \Drupal::service('renderer');
}
/**
* Sets the renderer.
*
* @param \Drupal\Core\Render\RendererInterface $renderer
* The new renderer.
*
* @return $this
*/
public function setRenderer(RendererInterface $renderer) {
$this->renderer = $renderer;
return $this;
}
/**
* Retrieves the theme manager.
*
* @return \Drupal\Core\Theme\ThemeManagerInterface
* The theme manager.
*/
protected function getThemeManager() {
return $this->themeManager ?: \Drupal::theme();
}
/**
* Sets the theme manager.
*
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
* The theme manager.
*
* @return $this
*/
protected function setThemeManager(ThemeManagerInterface $theme_manager) {
$this->themeManager = $theme_manager;
return $this;
}
/**
* Retrieves the theme initialization service.
*
* @return \Drupal\Core\Theme\ThemeInitializationInterface
* The theme initialization service.
*/
protected function getThemeInitializer() {
return $this->themeInitialization ?: \Drupal::service('theme.initialization');
}
/**
* Sets the theme initialization service.
*
* @param \Drupal\Core\Theme\ThemeInitializationInterface $theme_initialization
* The theme initialization service.
*
* @return $this
*/
protected function setThemeInitializer(ThemeInitializationInterface $theme_initialization) {
$this->themeInitialization = $theme_initialization;
return $this;
}
/**
* Retrieves the config factory service.
*
* @return \Drupal\Core\Config\ConfigFactoryInterface
* The config factory.
*/
protected function getConfigFactory() {
return $this->configFactory ?: \Drupal::configFactory();
}
/**
* Sets the config factory service.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
*
* @return $this
*/
protected function setConfigFactory(ConfigFactoryInterface $config_factory) {
$this->configFactory = $config_factory;
return $this;
}
// @todo Add a supportsIndex() implementation that checks whether there is
// actually any datasource present which supports viewing.
/**
* {@inheritdoc}
*/
public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) {
$properties = [];
if (!$datasource) {
$definition = [
'label' => $this->t('Rendered HTML output'),
'description' => $this->t('The complete HTML which would be displayed when viewing the item'),
'type' => 'search_api_html',
'processor_id' => $this->getPluginId(),
];
$properties['rendered_item'] = new RenderedItemProperty($definition);
}
return $properties;
}
/**
* {@inheritdoc}
*/
public function addFieldValues(ItemInterface $item) {
// Switch to the default theme in case the admin theme is enabled.
$active_theme = $this->getThemeManager()->getActiveTheme();
$default_theme = $this->getConfigFactory()
->get('system.theme')
->get('default');
$default_theme = $this->getThemeInitializer()
->getActiveThemeByName($default_theme);
$this->getThemeManager()->setActiveTheme($default_theme);
// Count of items that don't have a view mode.
$unset_view_modes = 0;
$fields = $this->getFieldsHelper()
->filterForPropertyPath($item->getFields(), NULL, 'rendered_item');
foreach ($fields as $field) {
$configuration = $field->getConfiguration();
// Change the current user to our dummy implementation to ensure we are
// using the configured roles.
$this->getAccountSwitcher()
->switchTo(new UserSession(['roles' => $configuration['roles']]));
$datasource_id = $item->getDatasourceId();
$datasource = $item->getDatasource();
$bundle = $datasource->getItemBundle($item->getOriginalObject());
// When no view mode has been set for the bundle, or it has been set to
// "Don't include the rendered item", skip this item.
if (empty($configuration['view_mode'][$datasource_id][$bundle])) {
// If it was really not set, also notify the user through the log.
if (!isset($configuration['view_mode'][$datasource_id][$bundle])) {
++$unset_view_modes;
}
continue;
}
else {
$view_mode = (string) $configuration['view_mode'][$datasource_id][$bundle];
}
try {
$build = $datasource->viewItem($item->getOriginalObject(), $view_mode);
// Add the excerpt to the render array to allow adding it to view modes.
if ($item->getExcerpt()) {
$build['#search_api_excerpt'] = $item->getExcerpt();
}
$value = (string) $this->getRenderer()->renderPlain($build);
if ($value) {
$field->addValue($value);
}
}
catch (\Exception $e) {
// This could throw all kinds of exceptions in specific scenarios, so we
// just catch all of them here. Not having a field value for this field
// probably makes sense in that case, so we just log an error and
// continue.
$variables = [
'%item_id' => $item->getId(),
'%view_mode' => $view_mode,
'%index' => $this->index->label(),
];
$this->logException($e, '%type while trying to render item %item_id with view mode %view_mode for search index %index: @message in %function (line %line of %file).', $variables);
}
}
// Restore the original user.
$this->getAccountSwitcher()->switchBack();
// Restore the original theme.
$this->getThemeManager()->setActiveTheme($active_theme);
if ($unset_view_modes > 0) {
$context = [
'%index' => $this->index->label(),
'%processor' => $this->label(),
'@count' => $unset_view_modes,
];
$this->getLogger()->warning('Warning: While indexing items on search index %index, @count item(s) did not have a view mode configured for one or more "Rendered item" fields.', $context);
}
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$this->dependencies = parent::calculateDependencies();
$fields = $this->getFieldsHelper()
->filterForPropertyPath($this->index->getFields(), NULL, 'rendered_item');
foreach ($fields as $field) {
$view_modes = $field->getConfiguration()['view_mode'];
foreach ($this->index->getDatasources() as $datasource_id => $datasource) {
if (($entity_type_id = $datasource->getEntityTypeId()) && !empty($view_modes[$datasource_id])) {
foreach ($view_modes[$datasource_id] as $view_mode) {
if ($view_mode) {
/** @var \Drupal\Core\Entity\EntityViewModeInterface $view_mode_entity */
$view_mode_entity = EntityViewMode::load($entity_type_id . '.' . $view_mode);
if ($view_mode_entity) {
$this->addDependency($view_mode_entity->getConfigDependencyKey(), $view_mode_entity->getConfigDependencyName());
}
}
}
}
}
}
return $this->dependencies;
}
/**
* {@inheritdoc}
*/
public function onDependencyRemoval(array $dependencies) {
// All dependencies of this processor are entity view modes, so we go
// through all of the index's fields using our property and remove the
// settings for all datasources or bundles which were set to one of the
// removed view modes. This will always result in the removal of all those
// dependencies.
// The code is highly similar to calculateDependencies(), only that we
// remove the setting (if necessary) instead of adding a dependency.
$fields = $this->getFieldsHelper()
->filterForPropertyPath($this->index->getFields(), NULL, 'rendered_item');
foreach ($fields as $field) {
$field_config = $field->getConfiguration();
$view_modes = $field_config['view_mode'];
foreach ($this->index->getDatasources() as $datasource_id => $datasource) {
if (!empty($view_modes[$datasource_id]) && ($entity_type_id = $datasource->getEntityTypeId())) {
foreach ($view_modes[$datasource_id] as $bundle => $view_mode_id) {
if ($view_mode_id) {
/** @var \Drupal\Core\Entity\EntityViewModeInterface $view_mode */
$view_mode = EntityViewMode::load($entity_type_id . '.' . $view_mode_id);
if ($view_mode) {
$dependency_key = $view_mode->getConfigDependencyKey();
$dependency_name = $view_mode->getConfigDependencyName();
if (!empty($dependencies[$dependency_key][$dependency_name])) {
unset($view_modes[$datasource_id][$bundle]);
}
}
}
}
}
}
$field_config['view_mode'] = $view_modes;
$field->setConfiguration($field_config);
}
return TRUE;
}
}
