View source
<?php
namespace Drupal\search_api_solr_devel\Controller;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\devel\DevelDumperManagerInterface;
use Drupal\search_api\Backend\BackendPluginManager;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\ServerInterface;
use Drupal\search_api\Utility\FieldsHelperInterface;
use Drupal\search_api\Utility\Utility;
use Symfony\Component\DependencyInjection\ContainerInterface;
class DevelController extends ControllerBase {
protected $storage;
protected $backendPluginManager;
protected $develDumperManager;
protected $fieldsHelper;
protected $moduleHandler;
protected $dateFormatter;
protected $time;
public function __construct(EntityTypeManagerInterface $entity_type_manager, BackendPluginManager $backend_plugin_manager, DevelDumperManagerInterface $devel_dumper_manager, FieldsHelperInterface $fields_helper, ModuleHandlerInterface $module_handler, DateFormatterInterface $date_formatter, TimeInterface $time) {
$this->storage = $entity_type_manager
->getStorage('search_api_server');
$this->backendPluginManager = $backend_plugin_manager;
$this->develDumperManager = $devel_dumper_manager;
$this->fieldsHelper = $fields_helper;
$this->moduleHandler = $module_handler;
$this->dateFormatter = $date_formatter;
$this->time = $time;
}
public static function create(ContainerInterface $container) {
return new static($container
->get('entity_type.manager'), $container
->get('plugin.manager.search_api.backend'), $container
->get('devel.dumper'), $container
->get('search_api.fields_helper'), $container
->get('module_handler'), $container
->get('date.formatter'), $container
->get('datetime.time'));
}
protected function getBackends() {
$backends = [];
$plugin_definitions = $this->backendPluginManager
->getDefinitions();
foreach ($plugin_definitions as $plugin_id => $plugin_definition) {
if (is_a($plugin_definition['class'], $plugin_definitions['search_api_solr']['class'], TRUE)) {
$backends[] = $plugin_id;
}
}
return $backends;
}
public function entitySolr(RouteMatchInterface $route_match) {
$output_details = [];
$table_rows = [];
$num = 0;
$parameter_name = $route_match
->getRouteObject()
->getOption('_devel_entity_type_id');
$entity = $route_match
->getParameter($parameter_name);
if ($entity && $entity instanceof EntityInterface) {
foreach ($this
->getBackends() as $backend_id) {
$servers = $this->storage
->loadByProperties([
'backend' => $backend_id,
'status' => TRUE,
]);
foreach ($servers as $server) {
$backend = $server
->getBackend();
$indexes = $server
->getIndexes();
$solr = $backend
->getSolrConnector();
foreach ($indexes as $index) {
if ($index
->status()) {
foreach ($index
->getDatasourceIds() as $datasource_id) {
list(, $entity_type) = Utility::splitPropertyPath($datasource_id);
if ($entity
->getEntityTypeId() == $entity_type) {
foreach (array_keys($entity
->getTranslationLanguages()) as $langcode) {
$item_id = $datasource_id . '/' . $entity
->id() . ':' . $langcode;
$items = [];
$base_summary_row = $this
->getBaseRow($server, $index, $datasource_id, $entity, $langcode, $item_id);
$items[$item_id] = $this->fieldsHelper
->createItemFromObject($index, $entity
->getTranslation($langcode)
->getTypedData(), $item_id);
$index
->alterIndexedItems($items);
$this->moduleHandler
->alter('search_api_index_items', $index, $items);
$index
->preprocessIndexItems($items);
$documents = $backend
->getDocuments($index, $items);
foreach ($documents as $document) {
$summary_row = $base_summary_row;
$summary_row['num'] = $num + 1;
$fields = $document
->getFields();
$summary_row['object_size'] = format_size(strlen(json_encode($fields)));
ksort($fields);
$details_id = $fields['id'];
$output_details[$details_id] = [
'#type' => 'details',
'#title' => $this
->t('Row #@num: local and Solr indexing data', [
'@num' => $num + 1,
]),
];
$output_details[$details_id][] = [
'#markup' => '<h3>' . $this
->t('Locally-generated data that would be sent to Solr during indexing:') . '</h3>',
];
$output_details[$details_id][] = [
'#markup' => $this->develDumperManager
->dumpOrExport($fields, $this
->t('Locally-generated data'), TRUE),
];
$output_details[$details_id][] = [
'#markup' => '<h3>' . $this
->t('Current data in Solr for this item:') . '</h3>',
];
$summary_row['solr_id'] = $fields['id'];
$query = $solr
->getSelectQuery();
$query
->setQuery('id:"' . $fields['id'] . '"');
$query
->setFields('*');
try {
$results = $solr
->execute($query, $backend
->getCollectionEndpoint($index));
$num_found = $results
->getNumFound();
$summary_row['solr_exists'] = $this
->t('yes');
} catch (\Exception $e) {
$results = [];
$num_found = -1;
$output_details[$details_id][] = [
'#markup' => $this
->t('Error querying the Solr server!') . '<pre>' . $e
->getMessage() . '</pre>',
];
$summary_row['solr_exists'] = $this
->t('error');
}
if ($num_found == 0) {
$summary_row['solr_exists'] = $this
->t('no');
$output_details[$details_id][] = [
'#markup' => $this
->t('No Solr document found with the ID %id', [
'%id' => $fields['id'],
]),
];
}
if ($num_found == 1) {
$solr_documents = $results
->getDocuments();
$fields = $solr_documents[0]
->getFields();
$summary_row['solr_size'] = format_size(strlen(json_encode($fields)));
if (!empty($fields['timestamp'])) {
$summary_row['solr_changed'] = $this
->showTimeAndTimeAgo(strtotime($fields['timestamp']));
}
ksort($fields);
$output_details[$details_id][] = [
'#markup' => $this->develDumperManager
->dumpOrExport($fields, $this
->t('Solr data'), TRUE),
];
}
$table_rows[$num++] = $summary_row;
}
}
}
}
}
}
}
}
}
if (empty($output_details)) {
return [
'#markup' => $this
->t('No enabled indexes with a Solr backend that contain this item were found.'),
];
}
return array_merge([
'#title' => $this
->t('Search API Solr devel status'),
'header' => [
'#markup' => $this
->t('This page shows Search API and Solr indexing data for the current entity.'),
],
'summary_table' => [
'#type' => 'table',
'#prefix' => '<h3>' . $this
->t('Summary') . '</h3><p>' . $this
->t("Each row in the table represents an item generated from this entity according to the Search API index configuration (data sources, fields, processors, hook implementations, etc.). The first columns correspond to the Search API tracking information (kept in the site's database), and the rest of the columns show information about the corresponding Solr document for each item. See the <a href=\"https://www.drupal.org/docs/8/modules/search-api/developer-documentation\">developer documentation</a>.") . '</p>',
'#header' => $this
->summaryTableHeader(),
'#rows' => $table_rows,
],
], $output_details);
}
protected function showTimeAndTimeAgo($timestamp) {
return $this
->t('%time (%time_ago ago)', [
'%time' => $this->dateFormatter
->format($timestamp),
'%time_ago' => $this->dateFormatter
->formatDiff($timestamp, $this->time
->getRequestTime()),
]);
}
protected function summaryTableHeader() {
return [
'num' => '#',
'server' => $this
->t('Search API server'),
'index' => $this
->t('Search API index'),
'id' => $this
->t('Item Datasource & ID'),
'langcode' => $this
->t('Item langcode'),
'tracked' => $this
->t('Is item tracked in SAPI Index?'),
'changed' => $this
->t('Last time item was marked to be indexed'),
'status' => $this
->t('Has item been sent to Solr?'),
'object_size' => $this
->t('Local: item size'),
'solr_id' => $this
->t('Solr: document ID'),
'solr_exists' => $this
->t('Solr: document exists?'),
'solr_changed' => $this
->t('Solr: time indexed'),
'solr_size' => $this
->t('Solr: document size'),
];
}
protected function getBaseRow(ServerInterface $server, IndexInterface $index, $datasource_id, EntityInterface $entity, $langcode, $item_id) {
$base_row = [
'num' => 0,
'server' => $this
->t('<a href=":url">@id</a>', [
'@id' => $server
->id(),
':url' => Url::fromRoute('entity.search_api_server.canonical', [
'search_api_server' => $server
->id(),
])
->toString(),
]),
'index' => $this
->t('<a href=":index_url">@index_id</a><br />(%read_write_mode mode)', [
'@index_id' => $index
->id(),
':index_url' => Url::fromRoute('entity.search_api_index.canonical', [
'search_api_index' => $index
->id(),
])
->toString(),
'%read_write_mode' => $index
->isReadOnly() ? $this
->t('read-only') : $this
->t('read-write'),
]),
'id' => $datasource_id . '/' . $entity
->id(),
'langcode' => $langcode,
'tracked' => '',
'changed' => '-',
'status' => '-',
'object_size' => '-',
'solr_id' => '-',
'solr_exists' => '',
'solr_changed' => '-',
'solr_size' => '-',
];
$tracker = $index
->getTrackerInstance();
$select = $tracker
->getDatabaseConnection()
->select('search_api_item', 'sai');
$select
->condition('index_id', $index
->id());
$select
->condition('datasource', $datasource_id);
$select
->condition('item_id', $item_id);
$select
->fields('sai', [
'item_id',
'status',
'changed',
]);
$tracker_data = $select
->execute()
->fetch();
if ($tracker_data) {
$base_row['tracked'] = $this
->t('yes');
$base_row['changed'] = $this
->showTimeAndTimeAgo($tracker_data->changed);
$base_row['status'] = $tracker_data->status ? $this
->t('no') : $this
->t('yes');
}
else {
$base_row['tracked'] = $this
->t('no');
}
return $base_row;
}
}