Add Solr search: EXT:solr 14 integration and JSON search endpoint
Apache Solr 10 runs on a dedicated VPS behind a Caddy HTTPS proxy (vitecsolr.evomedien.de) because the managed webserver only allows outgoing standard ports. Credentials stay out of git: the site config carries %env()% placeholders resolved via putenv() in the git-ignored config/system/additional.php. - composer: apache-solr-for-typo3/solr 14.0.0-RC1 (the only line compatible with TYPO3 14; final 14.0.0 will arrive via composer update) - config.yaml: read connection https/443, core_en per language, env placeholder credentials; .gitignore covers additional.php - setup.typoscript: config.index_enable = 1 (page indexing silently refuses without it), solr_pi_results JSON renderer registration, results highlighting, and content field extraction from tt_content rows - the headless JSON output has no TYPO3SEARCH markers, so the default page content extraction indexed an empty content field - SearchJsonRenderer (renderer #27): JSON output for the search plugin on /search. GET q/page in; { query, page, resultsPerPage, numFound, totalPages, results[], suggestions } out. Disables the page cache per request: config.no_cache is gone in TYPO3 v14, and q/page are excluded from cHash, so cached variants would collide - ext_localconf.php: q and page added to cacheHash excludedParameters - SolrIndexCommand (vitec:solr-index): works the index queue from the CLI with connection diagnostics and a --debug single-step mode - EXT:solr 14 ships no console commands and the backend button indexes one item per click
This commit is contained in:
130
packages/vitec/Classes/Command/SolrIndexCommand.php
Normal file
130
packages/vitec/Classes/Command/SolrIndexCommand.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Command;
|
||||
|
||||
use ApacheSolrForTypo3\Solr\ConnectionManager;
|
||||
use ApacheSolrForTypo3\Solr\Domain\Index\IndexService;
|
||||
use ApacheSolrForTypo3\Solr\Domain\Site\SiteRepository;
|
||||
use ApacheSolrForTypo3\Solr\IndexQueue\IndexingService;
|
||||
use ApacheSolrForTypo3\Solr\IndexQueue\Queue;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Throwable;
|
||||
use TYPO3\CMS\Core\Core\Bootstrap;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Works the Solr index queue from the CLI - EXT:solr 14 ships no console
|
||||
* commands, its indexing runs via backend module clicks (1 item each!) or a
|
||||
* scheduler task. This command fills that gap for cron usage and prints the
|
||||
* resolved connection state first, because a missing connection makes the
|
||||
* indexer skip items silently (no error, no queue entry).
|
||||
*/
|
||||
#[AsCommand(
|
||||
name: 'vitec:solr-index',
|
||||
description: 'Work the Solr index queue and show connection diagnostics',
|
||||
)]
|
||||
class SolrIndexCommand extends Command
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Max queue items to index in this run', '20');
|
||||
$this->addOption('root', 'r', InputOption::VALUE_REQUIRED, 'Root page uid of the site', '1');
|
||||
$this->addOption('debug', 'd', InputOption::VALUE_NONE, 'Single-step the first queue item verbosely');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
Bootstrap::initializeBackendAuthentication();
|
||||
|
||||
$rootPageId = (int)$input->getOption('root');
|
||||
$limit = (int)$input->getOption('limit');
|
||||
|
||||
$siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
|
||||
$site = $siteRepository->getSiteByRootPageId($rootPageId);
|
||||
|
||||
$configs = $site->getAllSolrConnectionConfigurations();
|
||||
$output->writeln('Connection configurations: ' . count($configs));
|
||||
foreach ($configs as $languageId => $config) {
|
||||
$read = $config['read'];
|
||||
$output->writeln(sprintf(
|
||||
' lang %d: %s://%s:%d%s core=%s auth=%s',
|
||||
$languageId,
|
||||
$read['scheme'],
|
||||
$read['host'],
|
||||
$read['port'],
|
||||
$read['path'],
|
||||
$read['core'],
|
||||
$read['username'] !== '' ? 'yes' : 'no',
|
||||
));
|
||||
}
|
||||
if ($configs === []) {
|
||||
$output->writeln('<error>No connection configuration resolved - check site config.</error>');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if ($input->getOption('debug')) {
|
||||
$queue = GeneralUtility::makeInstance(Queue::class);
|
||||
$items = $queue->getItemsToIndex($site, 5);
|
||||
$output->writeln('getItemsToIndex(5): ' . count($items) . ' items');
|
||||
if ($items !== []) {
|
||||
$first = $items[0];
|
||||
$output->writeln(' first: type=' . $first->getType() . ' uid=' . $first->getRecordUid());
|
||||
$svc = GeneralUtility::getContainer()->get(IndexingService::class);
|
||||
$ref = new \ReflectionObject($svc);
|
||||
|
||||
$call = static function (string $method, array $args) use ($svc, $ref) {
|
||||
$m = $ref->getMethod($method);
|
||||
return $m->invokeArgs($svc, $args);
|
||||
};
|
||||
|
||||
$conns = $call('getPageSolrConnections', [$first]);
|
||||
$output->writeln(' getPageSolrConnections: ' . count($conns) . ' connections (langs: ' . implode(',', array_keys($conns)) . ')');
|
||||
if ($conns === []) {
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$groups = $call('findUserGroupsForPage', [$first, 0]);
|
||||
$output->writeln(' findUserGroupsForPage(lang 0): ' . json_encode($groups));
|
||||
|
||||
$accessRootline = $call('buildAccessRootline', [$first, 0, $groups[0] ?? 0]);
|
||||
$params = $call('buildPageParameters', [$first]);
|
||||
$instructions = new \ApacheSolrForTypo3\Solr\IndexQueue\IndexingInstructions(
|
||||
items: [$first],
|
||||
action: \ApacheSolrForTypo3\Solr\IndexQueue\IndexingInstructions::ACTION_INDEX_PAGE,
|
||||
language: 0,
|
||||
userGroup: (int)($groups[0] ?? 0),
|
||||
accessRootline: $accessRootline,
|
||||
parameters: $params,
|
||||
);
|
||||
$response = $call('executeSubRequest', [$first, 0, $instructions]);
|
||||
if ($response === null) {
|
||||
$output->writeln(' executeSubRequest => NULL (siehe Log)');
|
||||
} else {
|
||||
$output->writeln(' executeSubRequest => HTTP ' . $response->getStatusCode()
|
||||
. ' content-type=' . $response->getHeaderLine('Content-Type'));
|
||||
$body = (string)$response->getBody();
|
||||
$output->writeln(' body (600 Zeichen): ' . substr($body, 0, 600));
|
||||
}
|
||||
}
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$indexService = GeneralUtility::makeInstance(IndexService::class, $site);
|
||||
try {
|
||||
$success = $indexService->indexItems($limit);
|
||||
} catch (Throwable $e) {
|
||||
$output->writeln('<error>indexItems threw: ' . $e->getMessage() . '</error>');
|
||||
$output->writeln($e->getTraceAsString());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$output->writeln('indexItems(' . $limit . ') returned: ' . ($success ? 'success' : 'with errors'));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user