first commit
This commit is contained in:
214
Classes/Controller/Backend/PageModuleSummary.php
Normal file
214
Classes/Controller/Backend/PageModuleSummary.php
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Controller\Backend;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2012-2015 Ingo Renner <ingo@typo3.org>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Backend\View\PageLayoutView;
|
||||
|
||||
/**
|
||||
* Summary to display flexform settings in the page layout backend module.
|
||||
*
|
||||
* @author Ingo Renner <ingo@typo3.org>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class PageModuleSummary
|
||||
{
|
||||
/**
|
||||
* PageLayoutView
|
||||
*
|
||||
* @var PageLayoutView
|
||||
*/
|
||||
protected $pageLayoutView;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $pluginContentElement = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $flexformData = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $settings = [];
|
||||
|
||||
/**
|
||||
* Returns information about a plugin's flexform configuration
|
||||
*
|
||||
* @param array $parameters Parameters to the hook
|
||||
* @return string Plugin configuration information
|
||||
*/
|
||||
public function getSummary(array $parameters)
|
||||
{
|
||||
$this->initialize($parameters['row'], $parameters['pObj']);
|
||||
|
||||
$this->addTargetPage();
|
||||
$this->addSettingFromFlexForm('Filter', 'search.query.filter');
|
||||
$this->addSettingFromFlexForm('Sorting', 'search.query.sortBy');
|
||||
$this->addSettingFromFlexForm('Results per Page', 'search.results.resultsPerPage');
|
||||
$this->addSettingFromFlexForm('Boost Function', 'search.query.boostFunction');
|
||||
$this->addSettingFromFlexForm('Boost Query', 'search.query.boostQuery');
|
||||
$this->addSettingFromFlexForm('Tie Breaker', 'search.query.tieParameter');
|
||||
$this->addSettingFromFlexForm('Template', 'view.templateFiles.results');
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $contentElement
|
||||
* @param PageLayoutView $pObj
|
||||
*/
|
||||
protected function initialize(array $contentElement, PageLayoutView $pObj)
|
||||
{
|
||||
$this->pageLayoutView = $pObj;
|
||||
|
||||
/** @var $service \TYPO3\CMS\Core\Service\FlexFormService::class */
|
||||
$service = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Service\FlexFormService::class);
|
||||
$this->flexformData = $service->convertFlexFormContentToArray($contentElement['pi_flexform']);
|
||||
$this->pluginContentElement = $contentElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the target page to the settings.
|
||||
*/
|
||||
protected function addTargetPage()
|
||||
{
|
||||
$targetPageId = $this->getFieldFromFlexform('search.targetPage');
|
||||
if (!empty($targetPageId)) {
|
||||
$page = BackendUtility::getRecord('pages', $targetPageId, 'title');
|
||||
$this->settings['Target Page'] = '[' . (int)$targetPageId . '] ' . $page['title'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $settingName
|
||||
* @param string $flexFormField
|
||||
*/
|
||||
protected function addSettingFromFlexForm($settingName, $flexFormField)
|
||||
{
|
||||
$value = $this->getFieldFromFlexform($flexFormField);
|
||||
|
||||
if (is_array($value)) {
|
||||
$value = $this->addSettingFromFlexFormArray($settingName, $value);
|
||||
}
|
||||
$this->addSettingIfNotEmpty($settingName, (string)$value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $settingName
|
||||
* @param array $values
|
||||
* @return bool
|
||||
*/
|
||||
protected function addSettingFromFlexFormArray($settingName, $values)
|
||||
{
|
||||
foreach ($values as $item) {
|
||||
if (!isset($item['field'])) {
|
||||
continue;
|
||||
}
|
||||
$field = $item['field'];
|
||||
|
||||
$label = $settingName . ' ';
|
||||
$label .= isset($field['field']) ? $field['field'] : '';
|
||||
$fieldValue = isset($field['value']) ? $field['value'] : '';
|
||||
$this->addSettingIfNotEmpty($label, (string)$fieldValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $settingName
|
||||
* @param string $value
|
||||
*/
|
||||
protected function addSettingIfNotEmpty($settingName, $value)
|
||||
{
|
||||
if (!empty($value)) {
|
||||
$this->settings[$settingName] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a field's value from flexform configuration, will check if
|
||||
* flexform configuration is available.
|
||||
*
|
||||
* @param string $path name of the field
|
||||
* @return string if nothing found, value if found
|
||||
*/
|
||||
protected function getFieldFromFlexform($path)
|
||||
{
|
||||
return ObjectAccess::getPropertyPath($this->flexformData, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function render()
|
||||
{
|
||||
/** @var $standaloneView StandaloneView */
|
||||
$standaloneView = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$standaloneView->setTemplatePathAndFilename(
|
||||
GeneralUtility::getFileAbsFileName('EXT:meilisearch/Resources/Private/Templates/Backend/PageModule/Summary.html')
|
||||
);
|
||||
|
||||
$standaloneView->assignMultiple([
|
||||
'pluginLabel' => $this->getPluginLabel(),
|
||||
'hidden' => $this->pluginContentElement['hidden'],
|
||||
'settings' => $this->settings,
|
||||
]);
|
||||
return $standaloneView->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin label
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getPluginLabel()
|
||||
{
|
||||
$label = BackendUtility::getLabelFromItemListMerged($this->pluginContentElement['pid'], 'tt_content', 'list_type', $this->pluginContentElement['list_type']);
|
||||
if (!empty($label)) {
|
||||
$label = $this->getLanguageService()->sL($label);
|
||||
} else {
|
||||
$label = sprintf($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'), $this->pluginContentElement['list_type']);
|
||||
}
|
||||
|
||||
return $this->pageLayoutView->linkEditContent(htmlspecialchars($label), $this->pluginContentElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the language service
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
328
Classes/Controller/Backend/Search/AbstractModuleController.php
Normal file
328
Classes/Controller/Backend/Search/AbstractModuleController.php
Normal file
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Controller\Backend\Search;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\ConnectionManager;
|
||||
use WapplerSystems\Meilisearch\Domain\Site\SiteRepository;
|
||||
use WapplerSystems\Meilisearch\Domain\Site\Site;
|
||||
use WapplerSystems\Meilisearch\System\Solr\SolrConnection as SolrCoreConnection;
|
||||
use WapplerSystems\Meilisearch\System\Mvc\Backend\Component\Exception\InvalidViewObjectNameException;
|
||||
use WapplerSystems\Meilisearch\System\Mvc\Backend\Service\ModuleDataStorageService;
|
||||
use TYPO3\CMS\Backend\Template\Components\Menu\Menu;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Backend\View\BackendTemplateView;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Messaging\AbstractMessage;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use TYPO3\CMS\Extbase\Mvc\View\NotFoundView;
|
||||
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
|
||||
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
|
||||
|
||||
/**
|
||||
* Abstract Module
|
||||
*
|
||||
* @property BackendTemplateView $view
|
||||
*/
|
||||
abstract class AbstractModuleController extends ActionController
|
||||
{
|
||||
/**
|
||||
* Backend Template Container
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultViewObjectName = BackendTemplateView::class;
|
||||
|
||||
/**
|
||||
* In the pagetree selected page UID
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $selectedPageUID;
|
||||
|
||||
/**
|
||||
* Holds the requested page UID because the selected page uid,
|
||||
* might be overwritten by the automatic site selection.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $requestedPageUID;
|
||||
|
||||
/**
|
||||
* @var Site
|
||||
*/
|
||||
protected $selectedSite;
|
||||
|
||||
/**
|
||||
* @var SiteRepository
|
||||
*/
|
||||
protected $siteRepository;
|
||||
|
||||
/**
|
||||
* @var SolrCoreConnection
|
||||
*/
|
||||
protected $selectedSolrCoreConnection;
|
||||
|
||||
/**
|
||||
* @var Menu
|
||||
*/
|
||||
protected $coreSelectorMenu = null;
|
||||
|
||||
/**
|
||||
* @var ConnectionManager
|
||||
*/
|
||||
protected $solrConnectionManager = null;
|
||||
|
||||
/**
|
||||
* @var ModuleDataStorageService
|
||||
*/
|
||||
protected $moduleDataStorageService = null;
|
||||
|
||||
/**
|
||||
* @param Site $selectedSite
|
||||
*/
|
||||
public function setSelectedSite(Site $selectedSite)
|
||||
{
|
||||
$this->selectedSite = $selectedSite;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SiteRepository $siteRepository
|
||||
*/
|
||||
public function injectSiteRepository(SiteRepository $siteRepository)
|
||||
{
|
||||
$this->siteRepository = $siteRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the controller and sets needed vars.
|
||||
*/
|
||||
protected function initializeAction()
|
||||
{
|
||||
parent::initializeAction();
|
||||
$this->solrConnectionManager = GeneralUtility::makeInstance(ConnectionManager::class);
|
||||
$this->moduleDataStorageService = GeneralUtility::makeInstance(ModuleDataStorageService::class);
|
||||
|
||||
$this->selectedPageUID = (int)GeneralUtility::_GP('id');
|
||||
if ($this->request->hasArgument('id')) {
|
||||
$this->selectedPageUID = (int)$this->request->getArgument('id');
|
||||
}
|
||||
|
||||
$this->requestedPageUID = $this->selectedPageUID;
|
||||
|
||||
if ($this->autoSelectFirstSiteAndRootPageWhenOnlyOneSiteIsAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->selectedPageUID < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->selectedSite = $this->siteRepository->getSiteByPageId($this->selectedPageUID);
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function autoSelectFirstSiteAndRootPageWhenOnlyOneSiteIsAvailable(): bool
|
||||
{
|
||||
if (count($this->siteRepository->getAvailableSites()) == 1) {
|
||||
$this->selectedSite = $this->siteRepository->getFirstAvailableSite();
|
||||
|
||||
// we only overwrite the selected pageUid when no id was passed
|
||||
if ($this->selectedPageUID === 0) {
|
||||
$this->selectedPageUID = $this->selectedSite->getRootPageId();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the doc header properly here
|
||||
*
|
||||
* @param ViewInterface $view
|
||||
* @return void
|
||||
*/
|
||||
protected function initializeView(ViewInterface $view)
|
||||
{
|
||||
parent::initializeView($view);
|
||||
$sites = $this->siteRepository->getAvailableSites();
|
||||
|
||||
$selectOtherPage = count($sites) > 0 || $this->selectedPageUID < 1;
|
||||
$this->view->assign('showSelectOtherPage', $selectOtherPage);
|
||||
$this->view->assign('pageUID', $this->selectedPageUID);
|
||||
if ($view instanceof NotFoundView || $this->selectedPageUID < 1) {
|
||||
return;
|
||||
}
|
||||
$this->view->getModuleTemplate()->addJavaScriptCode('mainJsFunctions', '
|
||||
top.fsMod.recentIds["searchbackend"] = ' . (int)$this->selectedPageUID . ';'
|
||||
);
|
||||
if (null === $this->selectedSite) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* @var BackendUserAuthentication $beUser */
|
||||
$beUser = $GLOBALS['BE_USER'];
|
||||
$permissionClause = $beUser->getPagePermsClause(1);
|
||||
$pageRecord = BackendUtility::readPageAccess($this->selectedSite->getRootPageId(), $permissionClause);
|
||||
|
||||
if (false === $pageRecord) {
|
||||
throw new \InvalidArgumentException(vsprintf('There is something wrong with permissions for page "%s" for backend user "%s".', [$this->selectedSite->getRootPageId(), $beUser->user['username']]), 1496146317);
|
||||
}
|
||||
$this->view->getModuleTemplate()->getDocHeaderComponent()->setMetaInformation($pageRecord);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates selector menu in backends doc header using selected page from page tree.
|
||||
*
|
||||
* @param string|null $uriToRedirectTo
|
||||
*/
|
||||
public function generateCoreSelectorMenuUsingPageTree(string $uriToRedirectTo = null)
|
||||
{
|
||||
if ($this->selectedPageUID < 1 || null === $this->selectedSite) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->view instanceof NotFoundView) {
|
||||
$this->initializeSelectedSolrCoreConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->generateCoreSelectorMenu($this->selectedSite, $uriToRedirectTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates Core selector Menu for given Site.
|
||||
*
|
||||
* @param Site $site
|
||||
* @param string|null $uriToRedirectTo
|
||||
* @throws InvalidViewObjectNameException
|
||||
*/
|
||||
protected function generateCoreSelectorMenu(Site $site, string $uriToRedirectTo = null)
|
||||
{
|
||||
if (!$this->view instanceof BackendTemplateView) {
|
||||
throw new InvalidViewObjectNameException(vsprintf(
|
||||
'The controller "%s" must use BackendTemplateView to be able to generate menu for backends docheader. \
|
||||
Please set `protected $defaultViewObjectName = BackendTemplateView::class;` field in your controller.',
|
||||
[static::class]), 1493804179);
|
||||
}
|
||||
$this->view->getModuleTemplate()->setFlashMessageQueue($this->controllerContext->getFlashMessageQueue());
|
||||
|
||||
$this->coreSelectorMenu = $this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
|
||||
$this->coreSelectorMenu->setIdentifier('component_core_selector_menu');
|
||||
|
||||
if (!isset($uriToRedirectTo)) {
|
||||
$uriToRedirectTo = $this->uriBuilder->reset()->uriFor();
|
||||
}
|
||||
|
||||
$this->initializeSelectedSolrCoreConnection();
|
||||
$cores = $this->solrConnectionManager->getConnectionsBySite($site);
|
||||
foreach ($cores as $core) {
|
||||
$coreAdmin = $core->getAdminService();
|
||||
$menuItem = $this->coreSelectorMenu->makeMenuItem();
|
||||
$menuItem->setTitle($coreAdmin->getCorePath());
|
||||
$uri = $this->uriBuilder->reset()->uriFor('switchCore',
|
||||
[
|
||||
'corePath' => $coreAdmin->getCorePath(),
|
||||
'uriToRedirectTo' => $uriToRedirectTo
|
||||
]
|
||||
);
|
||||
$menuItem->setHref($uri);
|
||||
|
||||
if ($coreAdmin->getCorePath() == $this->selectedSolrCoreConnection->getAdminService()->getCorePath()) {
|
||||
$menuItem->setActive(true);
|
||||
}
|
||||
$this->coreSelectorMenu->addMenuItem($menuItem);
|
||||
}
|
||||
|
||||
$this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->addMenu($this->coreSelectorMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches used core.
|
||||
*
|
||||
* Note: Does not check availability of core in site. All this stuff is done in the generation step.
|
||||
*
|
||||
* @param string $corePath
|
||||
* @param string $uriToRedirectTo
|
||||
*/
|
||||
public function switchCoreAction(string $corePath, string $uriToRedirectTo)
|
||||
{
|
||||
$moduleData = $this->moduleDataStorageService->loadModuleData();
|
||||
$moduleData->setCore($corePath);
|
||||
|
||||
$this->moduleDataStorageService->persistModuleData($moduleData);
|
||||
$message = LocalizationUtility::translate('coreselector_switched_successfully', 'solr', [$corePath]);
|
||||
$this->addFlashMessage($message);
|
||||
$this->redirectToUri($uriToRedirectTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the solr core connection considerately to the components state.
|
||||
* Uses and persists default core connection if persisted core in Site does not exist.
|
||||
*
|
||||
*/
|
||||
private function initializeSelectedSolrCoreConnection()
|
||||
{
|
||||
$moduleData = $this->moduleDataStorageService->loadModuleData();
|
||||
|
||||
$solrCoreConnections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
|
||||
$currentSolrCorePath = $moduleData->getCore();
|
||||
if (empty($currentSolrCorePath)) {
|
||||
$this->initializeFirstAvailableSolrCoreConnection($solrCoreConnections, $moduleData);
|
||||
return;
|
||||
}
|
||||
foreach ($solrCoreConnections as $solrCoreConnection) {
|
||||
if ($solrCoreConnection->getAdminService()->getCorePath() == $currentSolrCorePath) {
|
||||
$this->selectedSolrCoreConnection = $solrCoreConnection;
|
||||
}
|
||||
}
|
||||
if (!$this->selectedSolrCoreConnection instanceof SolrCoreConnection && count($solrCoreConnections) > 0) {
|
||||
$this->initializeFirstAvailableSolrCoreConnection($solrCoreConnections, $moduleData);
|
||||
$message = LocalizationUtility::translate('coreselector_switched_to_default_core', 'solr', [$currentSolrCorePath, $this->selectedSite->getLabel(), $this->selectedSolrCoreConnection->getAdminService()->getCorePath()]);
|
||||
$this->addFlashMessage($message, '', AbstractMessage::NOTICE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SolrCoreConnection[] $solrCoreConnections
|
||||
*/
|
||||
private function initializeFirstAvailableSolrCoreConnection(array $solrCoreConnections, $moduleData)
|
||||
{
|
||||
if (empty($solrCoreConnections)) {
|
||||
return;
|
||||
}
|
||||
$this->selectedSolrCoreConnection = $solrCoreConnections[0];
|
||||
$moduleData->setCore($this->selectedSolrCoreConnection->getAdminService()->getCorePath());
|
||||
$this->moduleDataStorageService->persistModuleData($moduleData);
|
||||
}
|
||||
}
|
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Controller\Backend\Search;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solrs-support@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Utility\ManagedResourcesUtility;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
|
||||
|
||||
/**
|
||||
* Manage Synonyms and Stop words in Backend Module
|
||||
* @property \TYPO3\CMS\Extbase\Mvc\Web\Response $response
|
||||
*/
|
||||
class CoreOptimizationModuleController extends AbstractModuleController
|
||||
{
|
||||
/**
|
||||
* Set up the doc header properly here
|
||||
*
|
||||
* @param ViewInterface $view
|
||||
* @return void
|
||||
*/
|
||||
protected function initializeView(ViewInterface $view)
|
||||
{
|
||||
parent::initializeView($view);
|
||||
|
||||
$this->generateCoreSelectorMenuUsingPageTree();
|
||||
/* @var ModuleTemplate $module */ // holds the state of chosen tab
|
||||
$module = $this->objectManager->get(ModuleTemplate::class);
|
||||
$coreOptimizationTabs = $module->getDynamicTabMenu([], 'coreOptimization');
|
||||
$this->view->assign('tabs', $coreOptimizationTabs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets synonyms and stopwords for the currently selected core
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
if ($this->selectedSolrCoreConnection === null) {
|
||||
$this->view->assign('can_not_proceed', true);
|
||||
return;
|
||||
}
|
||||
|
||||
$synonyms = [];
|
||||
$coreAdmin = $this->selectedSolrCoreConnection->getAdminService();
|
||||
$rawSynonyms = $coreAdmin->getSynonyms();
|
||||
foreach ($rawSynonyms as $baseWord => $synonymList) {
|
||||
$synonyms[$baseWord] = implode(', ', $synonymList);
|
||||
}
|
||||
|
||||
$stopWords = $coreAdmin->getStopWords();
|
||||
$this->view->assignMultiple([
|
||||
'synonyms' => $synonyms,
|
||||
'stopWords' => implode(PHP_EOL, $stopWords),
|
||||
'stopWordsCount' => count($stopWords)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add synonyms to selected core
|
||||
*
|
||||
* @param string $baseWord
|
||||
* @param string $synonyms
|
||||
* @param bool $overrideExisting
|
||||
* @return void
|
||||
*/
|
||||
public function addSynonymsAction(string $baseWord, string $synonyms, $overrideExisting)
|
||||
{
|
||||
if (empty($baseWord) || empty($synonyms)) {
|
||||
$this->addFlashMessage(
|
||||
'Please provide a base word and synonyms.',
|
||||
'Missing parameter',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
} else {
|
||||
$baseWord = mb_strtolower($baseWord);
|
||||
$synonyms = mb_strtolower($synonyms);
|
||||
|
||||
$coreAdmin = $this->selectedSolrCoreConnection->getAdminService();
|
||||
if ($overrideExisting && $coreAdmin->getSynonyms($baseWord)) {
|
||||
$coreAdmin->deleteSynonym($baseWord);
|
||||
}
|
||||
$coreAdmin->addSynonym($baseWord, GeneralUtility::trimExplode(',', $synonyms, true));
|
||||
$coreAdmin->reloadCore();
|
||||
|
||||
$this->addFlashMessage(
|
||||
'"' . $synonyms . '" added as synonyms for base word "' . $baseWord . '"'
|
||||
);
|
||||
}
|
||||
|
||||
$this->redirect('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fileFormat
|
||||
* @return void
|
||||
*/
|
||||
public function exportStopWordsAction($fileFormat = 'txt')
|
||||
{
|
||||
$coreAdmin = $this->selectedSolrCoreConnection->getAdminService();
|
||||
$this->exportFile(
|
||||
implode(PHP_EOL, $coreAdmin->getStopWords()),
|
||||
'stopwords',
|
||||
$fileFormat
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports synonyms to a download file.
|
||||
*
|
||||
* @param string $fileFormat
|
||||
* @return string
|
||||
*/
|
||||
public function exportSynonymsAction($fileFormat = 'txt')
|
||||
{
|
||||
$coreAdmin = $this->selectedSolrCoreConnection->getAdminService();
|
||||
$synonyms = $coreAdmin->getSynonyms();
|
||||
return $this->exportFile(ManagedResourcesUtility::exportSynonymsToTxt($synonyms), 'synonyms', $fileFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $synonymFileUpload
|
||||
* @param bool $overrideExisting
|
||||
* @param bool $deleteSynonymsBefore
|
||||
* @return void
|
||||
*/
|
||||
public function importSynonymListAction(array $synonymFileUpload, $overrideExisting, $deleteSynonymsBefore)
|
||||
{
|
||||
if ($deleteSynonymsBefore) {
|
||||
$this->deleteAllSynonyms();
|
||||
}
|
||||
|
||||
$fileLines = ManagedResourcesUtility::importSynonymsFromPlainTextContents($synonymFileUpload);
|
||||
$synonymCount = 0;
|
||||
|
||||
$coreAdmin = $this->selectedSolrCoreConnection->getAdminService();
|
||||
foreach ($fileLines as $baseWord => $synonyms) {
|
||||
if (!isset($baseWord) || empty($synonyms)) {
|
||||
continue;
|
||||
}
|
||||
$this->deleteExistingSynonym($overrideExisting, $deleteSynonymsBefore, $baseWord);
|
||||
$coreAdmin->addSynonym($baseWord, $synonyms);
|
||||
$synonymCount++;
|
||||
}
|
||||
|
||||
$coreAdmin->reloadCore();
|
||||
$this->addFlashMessage(
|
||||
$synonymCount . ' synonyms imported.'
|
||||
);
|
||||
$this->redirect('index');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $stopwordsFileUpload
|
||||
* @param bool $replaceStopwords
|
||||
* @return void
|
||||
*/
|
||||
public function importStopWordListAction(array $stopwordsFileUpload, $replaceStopwords)
|
||||
{
|
||||
$this->saveStopWordsAction(
|
||||
ManagedResourcesUtility::importStopwordsFromPlainTextContents($stopwordsFileUpload),
|
||||
$replaceStopwords
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete complete synonym list
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteAllSynonymsAction()
|
||||
{
|
||||
$allSynonymsCouldBeDeleted = $this->deleteAllSynonyms();
|
||||
|
||||
$coreAdmin = $this->selectedSolrCoreConnection->getAdminService();
|
||||
$reloadResponse = $coreAdmin->reloadCore();
|
||||
|
||||
if ($allSynonymsCouldBeDeleted
|
||||
&& $reloadResponse->getHttpStatus() == 200
|
||||
) {
|
||||
$this->addFlashMessage(
|
||||
'All synonym removed.'
|
||||
);
|
||||
} else {
|
||||
$this->addFlashMessage(
|
||||
'Failed to remove all synonyms.',
|
||||
'An error occurred',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
}
|
||||
$this->redirect('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a synonym mapping by its base word.
|
||||
*
|
||||
* @param string $baseWord Synonym mapping base word
|
||||
*/
|
||||
public function deleteSynonymsAction($baseWord)
|
||||
{
|
||||
$coreAdmin = $this->selectedSolrCoreConnection->getAdminService();
|
||||
$deleteResponse = $coreAdmin->deleteSynonym($baseWord);
|
||||
$reloadResponse = $coreAdmin->reloadCore();
|
||||
|
||||
if ($deleteResponse->getHttpStatus() == 200
|
||||
&& $reloadResponse->getHttpStatus() == 200
|
||||
) {
|
||||
$this->addFlashMessage(
|
||||
'Synonym removed.'
|
||||
);
|
||||
} else {
|
||||
$this->addFlashMessage(
|
||||
'Failed to remove synonym.',
|
||||
'An error occurred',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
}
|
||||
|
||||
$this->redirect('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the edited stop word list to Solr
|
||||
*
|
||||
* @param string $stopWords
|
||||
* @param bool $replaceStopwords
|
||||
* @return void
|
||||
*/
|
||||
public function saveStopWordsAction(string $stopWords, $replaceStopwords = true)
|
||||
{
|
||||
// lowercase stopword before saving because terms get lowercased before stopword filtering
|
||||
$newStopWords = mb_strtolower($stopWords);
|
||||
$newStopWords = GeneralUtility::trimExplode("\n", $newStopWords, true);
|
||||
|
||||
$coreAdmin = $this->selectedSolrCoreConnection->getAdminService();
|
||||
$oldStopWords = $coreAdmin->getStopWords();
|
||||
|
||||
if ($replaceStopwords) {
|
||||
$removedStopWords = array_diff($oldStopWords, $newStopWords);
|
||||
$wordsRemoved = $this->removeStopsWordsFromIndex($removedStopWords);
|
||||
} else {
|
||||
$wordsRemoved = true;
|
||||
}
|
||||
|
||||
$wordsAdded = true;
|
||||
$addedStopWords = array_diff($newStopWords, $oldStopWords);
|
||||
if (!empty($addedStopWords)) {
|
||||
$wordsAddedResponse = $coreAdmin->addStopWords($addedStopWords);
|
||||
$wordsAdded = ($wordsAddedResponse->getHttpStatus() == 200);
|
||||
}
|
||||
|
||||
$reloadResponse = $coreAdmin->reloadCore();
|
||||
if ($wordsRemoved && $wordsAdded && $reloadResponse->getHttpStatus() == 200) {
|
||||
$this->addFlashMessage(
|
||||
'Stop Words Updated.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->redirect('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param string $type
|
||||
* @param string $fileExtension
|
||||
* @return string
|
||||
*/
|
||||
protected function exportFile($content, $type = 'synonyms', $fileExtension = 'txt') : string
|
||||
{
|
||||
$coreAdmin = $this->selectedSolrCoreConnection->getAdminService();
|
||||
|
||||
$this->response->setHeader('Content-type', 'text/plain', true);
|
||||
$this->response->setHeader('Cache-control', 'public', true);
|
||||
$this->response->setHeader('Content-Description', 'File transfer', true);
|
||||
$this->response->setHeader(
|
||||
'Content-disposition',
|
||||
'attachment; filename =' . $type . '_' .
|
||||
$coreAdmin->getPrimaryEndpoint()->getCore() . '.' . $fileExtension,
|
||||
true
|
||||
);
|
||||
|
||||
$this->response->setContent($content);
|
||||
$this->sendFileResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method send the headers and content and does an exit, since without the exit TYPO3 produces and error.
|
||||
* @return void
|
||||
*/
|
||||
protected function sendFileResponse()
|
||||
{
|
||||
$this->response->sendHeaders();
|
||||
$this->response->send();
|
||||
$this->response->shutdown();
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete complete synonym list form solr
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function deleteAllSynonyms() : bool
|
||||
{
|
||||
$coreAdmin = $this->selectedSolrCoreConnection->getAdminService();
|
||||
$synonyms = $coreAdmin->getSynonyms();
|
||||
$allSynonymsCouldBeDeleted = true;
|
||||
|
||||
foreach ($synonyms as $baseWord => $synonym) {
|
||||
$deleteResponse = $coreAdmin->deleteSynonym($baseWord);
|
||||
$allSynonymsCouldBeDeleted = $allSynonymsCouldBeDeleted && $deleteResponse->getHttpStatus() == 200;
|
||||
}
|
||||
|
||||
return $allSynonymsCouldBeDeleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $stopwordsToRemove
|
||||
* @return bool
|
||||
*/
|
||||
protected function removeStopsWordsFromIndex($stopwordsToRemove) : bool
|
||||
{
|
||||
$wordsRemoved = true;
|
||||
$coreAdmin = $this->selectedSolrCoreConnection->getAdminService();
|
||||
|
||||
foreach ($stopwordsToRemove as $word) {
|
||||
$response = $coreAdmin->deleteStopWord($word);
|
||||
if ($response->getHttpStatus() != 200) {
|
||||
$wordsRemoved = false;
|
||||
$this->addFlashMessage(
|
||||
'Failed to remove stop word "' . $word . '".',
|
||||
'An error occurred',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $wordsRemoved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete synonym entry if selceted before
|
||||
* @param bool $overrideExisting
|
||||
* @param bool $deleteSynonymsBefore
|
||||
* @param string $baseWord
|
||||
*/
|
||||
protected function deleteExistingSynonym($overrideExisting, $deleteSynonymsBefore, $baseWord)
|
||||
{
|
||||
$coreAdmin = $this->selectedSolrCoreConnection->getAdminService();
|
||||
|
||||
if (!$deleteSynonymsBefore &&
|
||||
$overrideExisting &&
|
||||
$coreAdmin->getSynonyms($baseWord)
|
||||
) {
|
||||
$coreAdmin->deleteSynonym($baseWord);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Controller\Backend\Search;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2013-2015 Ingo Renner <ingo@typo3.org>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\ConnectionManager;
|
||||
use WapplerSystems\Meilisearch\IndexQueue\Queue;
|
||||
use WapplerSystems\Meilisearch\System\Solr\SolrConnection;
|
||||
use WapplerSystems\Meilisearch\Util;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder as BackendUriBuilder;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\Web\ReferringRequest;
|
||||
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
|
||||
|
||||
/**
|
||||
* Index Administration Module
|
||||
*
|
||||
* @author Ingo Renner <ingo@typo3.org>
|
||||
*/
|
||||
class IndexAdministrationModuleController extends AbstractModuleController
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Queue
|
||||
*/
|
||||
protected $indexQueue;
|
||||
|
||||
/**
|
||||
* @var ConnectionManager
|
||||
*/
|
||||
protected $solrConnectionManager = null;
|
||||
|
||||
/**
|
||||
* @param ConnectionManager $solrConnectionManager
|
||||
*/
|
||||
public function setSolrConnectionManager(ConnectionManager $solrConnectionManager)
|
||||
{
|
||||
$this->solrConnectionManager = $solrConnectionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the controller before invoking an action method.
|
||||
*/
|
||||
protected function initializeAction()
|
||||
{
|
||||
parent::initializeAction();
|
||||
$this->indexQueue = GeneralUtility::makeInstance(Queue::class);
|
||||
$this->solrConnectionManager = GeneralUtility::makeInstance(ConnectionManager::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index action, shows an overview of available index maintenance operations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
if ($this->selectedSite === null || empty($this->solrConnectionManager->getConnectionsBySite($this->selectedSite))) {
|
||||
$this->view->assign('can_not_proceed', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the site's indexes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function emptyIndexAction()
|
||||
{
|
||||
$siteHash = $this->selectedSite->getSiteHash();
|
||||
|
||||
try {
|
||||
$affectedCores = [];
|
||||
$solrServers = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
|
||||
foreach ($solrServers as $solrServer) {
|
||||
$writeService = $solrServer->getWriteService();
|
||||
/* @var $solrServer SolrConnection */
|
||||
$writeService->deleteByQuery('siteHash:' . $siteHash);
|
||||
$writeService->commit(false, false, false);
|
||||
$affectedCores[] = $writeService->getPrimaryEndpoint()->getCore();
|
||||
}
|
||||
$message = LocalizationUtility::translate('solr.backend.index_administration.index_emptied_all', 'Solr', [$this->selectedSite->getLabel(), implode(', ', $affectedCores)]);
|
||||
$this->addFlashMessage($message);
|
||||
} catch (\Exception $e) {
|
||||
$this->addFlashMessage(LocalizationUtility::translate('solr.backend.index_administration.error.on_empty_index', 'Solr', [$e->__toString()]), '', FlashMessage::ERROR);
|
||||
}
|
||||
|
||||
$this->redirect('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the Index Queue
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearIndexQueueAction()
|
||||
{
|
||||
$this->indexQueue->deleteItemsBySite($this->selectedSite);
|
||||
$this->addFlashMessage(
|
||||
LocalizationUtility::translate('solr.backend.index_administration.success.queue_emptied', 'Solr',
|
||||
[$this->selectedSite->getLabel()])
|
||||
);
|
||||
$this->redirectToReferrerModule();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the site's Solr cores.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reloadIndexConfigurationAction()
|
||||
{
|
||||
$coresReloaded = true;
|
||||
$reloadedCores = [];
|
||||
$solrServers = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
|
||||
|
||||
foreach ($solrServers as $solrServer) {
|
||||
/* @var $solrServer SolrConnection */
|
||||
$coreAdmin = $solrServer->getAdminService();
|
||||
$coreReloaded = $coreAdmin->reloadCore()->getHttpStatus() === 200;
|
||||
|
||||
$coreName = $coreAdmin->getPrimaryEndpoint()->getCore();
|
||||
if (!$coreReloaded) {
|
||||
$coresReloaded = false;
|
||||
|
||||
$this->addFlashMessage(
|
||||
'Failed to reload index configuration for core "' . $coreName . '"',
|
||||
'',
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
$reloadedCores[] = $coreName;
|
||||
}
|
||||
|
||||
if ($coresReloaded) {
|
||||
$this->addFlashMessage(
|
||||
'Core configuration reloaded (' . implode(', ', $reloadedCores) . ').',
|
||||
'',
|
||||
FlashMessage::OK
|
||||
);
|
||||
}
|
||||
|
||||
$this->redirect('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to the referrer module index Action.
|
||||
*
|
||||
* Fluids <f:form VH can not make urls to other modules properly.
|
||||
* The module name/key is not provided in the hidden fields __referrer by bulding form.
|
||||
* So this is currently the single way to make it possible.
|
||||
*
|
||||
* @todo: remove this method if f:form works properly between backend modules.
|
||||
*/
|
||||
protected function redirectToReferrerModule()
|
||||
{
|
||||
$wasFromQueue = $this->request->hasArgument('fromQueue');
|
||||
if (!$wasFromQueue) {
|
||||
$this->redirect('index');
|
||||
return;
|
||||
}
|
||||
|
||||
/* @var BackendUriBuilder $backendUriBuilder */
|
||||
$backendUriBuilder = GeneralUtility::makeInstance(BackendUriBuilder::class);
|
||||
|
||||
$parameters = ['id' => $this->selectedPageUID];
|
||||
$referringUri = $backendUriBuilder->buildUriFromRoute('searchbackend_SolrIndexqueue', $parameters);
|
||||
|
||||
$this->redirectToUri($referringUri);
|
||||
}
|
||||
}
|
287
Classes/Controller/Backend/Search/IndexQueueModuleController.php
Normal file
287
Classes/Controller/Backend/Search/IndexQueueModuleController.php
Normal file
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Controller\Backend\Search;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2013-2015 Ingo Renner <ingo@typo3.org>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Backend\IndexingConfigurationSelectorField;
|
||||
use WapplerSystems\Meilisearch\Domain\Index\IndexService;
|
||||
use WapplerSystems\Meilisearch\IndexQueue\Queue;
|
||||
use TYPO3\CMS\Backend\View\BackendTemplateView;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
|
||||
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
|
||||
|
||||
/**
|
||||
* Index Queue Module
|
||||
*
|
||||
* @author Ingo Renner <ingo@typo3.org>
|
||||
* @property BackendTemplateView $view
|
||||
*/
|
||||
class IndexQueueModuleController extends AbstractModuleController
|
||||
{
|
||||
|
||||
/**
|
||||
* Module name, used to identify a module f.e. in URL parameters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $moduleName = 'IndexQueue';
|
||||
|
||||
/**
|
||||
* Module title, shows up in the module menu.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $moduleTitle = 'Index Queue';
|
||||
|
||||
/**
|
||||
* @var Queue
|
||||
*/
|
||||
protected $indexQueue;
|
||||
|
||||
/**
|
||||
* Initializes the controller before invoking an action method.
|
||||
*/
|
||||
protected function initializeAction()
|
||||
{
|
||||
parent::initializeAction();
|
||||
$this->indexQueue = GeneralUtility::makeInstance(Queue::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Queue $indexQueue
|
||||
*/
|
||||
public function setIndexQueue(Queue $indexQueue)
|
||||
{
|
||||
$this->indexQueue = $indexQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the doc header properly here
|
||||
*
|
||||
* @param ViewInterface $view
|
||||
* @return void
|
||||
*/
|
||||
protected function initializeView(ViewInterface $view)
|
||||
{
|
||||
parent::initializeView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the available indexing configurations
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
if (!$this->canQueueSelectedSite()) {
|
||||
$this->view->assign('can_not_proceed', true);
|
||||
return;
|
||||
}
|
||||
|
||||
$statistics = $this->indexQueue->getStatisticsBySite($this->selectedSite);
|
||||
$this->view->assign('indexQueueInitializationSelector', $this->getIndexQueueInitializationSelector());
|
||||
$this->view->assign('indexqueue_statistics', $statistics);
|
||||
$this->view->assign('indexqueue_errors', $this->indexQueue->getErrorsBySite($this->selectedSite));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if selected site can be queued.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function canQueueSelectedSite()
|
||||
{
|
||||
if ($this->selectedSite === null || empty($this->solrConnectionManager->getConnectionsBySite($this->selectedSite))) {
|
||||
return false;
|
||||
}
|
||||
$enabledIndexQueueConfigurationNames = $this->selectedSite->getSolrConfiguration()->getEnabledIndexQueueConfigurationNames();
|
||||
if (empty($enabledIndexQueueConfigurationNames)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a field to select which indexing configurations to initialize.
|
||||
*
|
||||
* Uses TCEforms.
|
||||
*
|
||||
* @return string Markup for the select field
|
||||
*/
|
||||
protected function getIndexQueueInitializationSelector()
|
||||
{
|
||||
$selector = GeneralUtility::makeInstance(IndexingConfigurationSelectorField::class, /** @scrutinizer ignore-type */ $this->selectedSite);
|
||||
$selector->setFormElementName('tx_meilisearch-index-queue-initialization');
|
||||
|
||||
return $selector->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Index Queue for selected indexing configurations
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initializeIndexQueueAction()
|
||||
{
|
||||
$initializedIndexingConfigurations = [];
|
||||
|
||||
$indexingConfigurationsToInitialize = GeneralUtility::_POST('tx_meilisearch-index-queue-initialization');
|
||||
if ((!empty($indexingConfigurationsToInitialize)) && (is_array($indexingConfigurationsToInitialize))) {
|
||||
// initialize selected indexing configuration
|
||||
$initializedIndexingConfigurations = $this->indexQueue->getInitializationService()->initializeBySiteAndIndexConfigurations($this->selectedSite, $indexingConfigurationsToInitialize);
|
||||
} else {
|
||||
$messageLabel = 'solr.backend.index_queue_module.flashmessage.initialize.no_selection';
|
||||
$titleLabel = 'solr.backend.index_queue_module.flashmessage.not_initialized.title';
|
||||
$this->addFlashMessage(
|
||||
LocalizationUtility::translate($messageLabel, 'Solr'),
|
||||
LocalizationUtility::translate($titleLabel, 'Solr'),
|
||||
FlashMessage::WARNING
|
||||
);
|
||||
}
|
||||
$messagesForConfigurations = [];
|
||||
foreach (array_keys($initializedIndexingConfigurations) as $indexingConfigurationName) {
|
||||
$itemCount = $this->indexQueue->getStatisticsBySite($this->selectedSite, $indexingConfigurationName)->getTotalCount();
|
||||
$messagesForConfigurations[] = $indexingConfigurationName . ' (' . $itemCount . ' records)';
|
||||
}
|
||||
|
||||
if (!empty($initializedIndexingConfigurations)) {
|
||||
$messageLabel = 'solr.backend.index_queue_module.flashmessage.initialize.success';
|
||||
$titleLabel = 'solr.backend.index_queue_module.flashmessage.initialize.title';
|
||||
$this->addFlashMessage(
|
||||
LocalizationUtility::translate($messageLabel, 'Solr', [implode(', ', $messagesForConfigurations)]),
|
||||
LocalizationUtility::translate($titleLabel, 'Solr'),
|
||||
FlashMessage::OK
|
||||
);
|
||||
}
|
||||
|
||||
$this->redirect('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all errors in the index queue list. So that the items can be indexed again.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resetLogErrorsAction()
|
||||
{
|
||||
$resetResult = $this->indexQueue->resetAllErrors();
|
||||
|
||||
$label = 'solr.backend.index_queue_module.flashmessage.success.reset_errors';
|
||||
$severity = FlashMessage::OK;
|
||||
if (!$resetResult) {
|
||||
$label = 'solr.backend.index_queue_module.flashmessage.error.reset_errors';
|
||||
$severity = FlashMessage::ERROR;
|
||||
}
|
||||
|
||||
$this->addIndexQueueFlashMessage($label, $severity);
|
||||
|
||||
$this->redirect('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* ReQueues a single item in the indexQueue.
|
||||
*
|
||||
* @param string $type
|
||||
* @param int $uid
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function requeueDocumentAction(string $type, int $uid)
|
||||
{
|
||||
$label = 'solr.backend.index_queue_module.flashmessage.error.single_item_not_requeued';
|
||||
$severity = FlashMessage::ERROR;
|
||||
|
||||
$updateCount = $this->indexQueue->updateItem($type, $uid, time());
|
||||
if ($updateCount > 0) {
|
||||
$label = 'solr.backend.index_queue_module.flashmessage.success.single_item_was_requeued';
|
||||
$severity = FlashMessage::OK;
|
||||
}
|
||||
|
||||
$this->addIndexQueueFlashMessage($label, $severity);
|
||||
|
||||
$this->redirect('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the error message for one queue item.
|
||||
*
|
||||
* @param int $indexQueueItemId
|
||||
* @return void
|
||||
*/
|
||||
public function showErrorAction(int $indexQueueItemId)
|
||||
{
|
||||
if (is_null($indexQueueItemId)) {
|
||||
// add a flash message and quit
|
||||
$label = 'solr.backend.index_queue_module.flashmessage.error.no_queue_item_for_queue_error';
|
||||
$severity = FlashMessage::ERROR;
|
||||
$this->addIndexQueueFlashMessage($label, $severity);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$item = $this->indexQueue->getItem($indexQueueItemId);
|
||||
$this->view->assign('indexQueueItem', $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexes a few documents with the index service.
|
||||
* @return void
|
||||
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException
|
||||
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException
|
||||
*/
|
||||
public function doIndexingRunAction()
|
||||
{
|
||||
/** @var $indexService \WapplerSystems\Meilisearch\Domain\Index\IndexService */
|
||||
$indexService = GeneralUtility::makeInstance(IndexService::class, /** @scrutinizer ignore-type */ $this->selectedSite);
|
||||
$indexWithoutErrors = $indexService->indexItems(10);
|
||||
|
||||
$label = 'solr.backend.index_queue_module.flashmessage.success.index_manual';
|
||||
$severity = FlashMessage::OK;
|
||||
if (!$indexWithoutErrors) {
|
||||
$label = 'solr.backend.index_queue_module.flashmessage.error.index_manual';
|
||||
$severity = FlashMessage::ERROR;
|
||||
}
|
||||
|
||||
$this->addFlashMessage(
|
||||
LocalizationUtility::translate($label, 'Solr'),
|
||||
LocalizationUtility::translate('solr.backend.index_queue_module.flashmessage.index_manual', 'Solr'),
|
||||
$severity
|
||||
);
|
||||
|
||||
$this->redirect('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a flash message for the index queue module.
|
||||
*
|
||||
* @param string $label
|
||||
* @param int $severity
|
||||
*/
|
||||
protected function addIndexQueueFlashMessage($label, $severity)
|
||||
{
|
||||
$this->addFlashMessage(LocalizationUtility::translate($label, 'Solr'), LocalizationUtility::translate('solr.backend.index_queue_module.flashmessage.title', 'Solr'), $severity);
|
||||
}
|
||||
}
|
324
Classes/Controller/Backend/Search/InfoModuleController.php
Normal file
324
Classes/Controller/Backend/Search/InfoModuleController.php
Normal file
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Controller\Backend\Search;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Api;
|
||||
use WapplerSystems\Meilisearch\ConnectionManager;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Statistics\StatisticsRepository;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ApacheSolrDocument\Repository;
|
||||
use WapplerSystems\Meilisearch\System\Solr\ResponseAdapter;
|
||||
use WapplerSystems\Meilisearch\System\Validator\Path;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
|
||||
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
|
||||
|
||||
/**
|
||||
* Info Module
|
||||
*/
|
||||
class InfoModuleController extends AbstractModuleController
|
||||
{
|
||||
/**
|
||||
* @var ConnectionManager
|
||||
*/
|
||||
protected $solrConnectionManager;
|
||||
|
||||
/**
|
||||
* @var Repository
|
||||
*/
|
||||
protected $apacheSolrDocumentRepository;
|
||||
|
||||
/**
|
||||
* Initializes the controller before invoking an action method.
|
||||
*/
|
||||
protected function initializeAction()
|
||||
{
|
||||
parent::initializeAction();
|
||||
$this->solrConnectionManager = GeneralUtility::makeInstance(ConnectionManager::class);
|
||||
$this->apacheSolrDocumentRepository = GeneralUtility::makeInstance(Repository::class);
|
||||
|
||||
}
|
||||
/**
|
||||
* Set up the doc header properly here
|
||||
*
|
||||
* @param ViewInterface $view
|
||||
* @return void
|
||||
*/
|
||||
protected function initializeView(ViewInterface $view)
|
||||
{
|
||||
parent::initializeView($view);
|
||||
|
||||
/* @var ModuleTemplate $module */ // holds the state of chosen tab
|
||||
$module = GeneralUtility::makeInstance(ModuleTemplate::class);
|
||||
$coreOptimizationTabs = $module->getDynamicTabMenu([], 'coreOptimization');
|
||||
$this->view->assign('tabs', $coreOptimizationTabs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index action, shows an overview of the state of the Solr index
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
if ($this->selectedSite === null) {
|
||||
$this->view->assign('can_not_proceed', true);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->collectConnectionInfos();
|
||||
$this->collectStatistics();
|
||||
$this->collectIndexFieldsInfo();
|
||||
$this->collectIndexInspectorInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param int $uid
|
||||
* @param int $pageId
|
||||
* @param int $languageUid
|
||||
* @return void
|
||||
*/
|
||||
public function documentsDetailsAction(string $type, int $uid, int $pageId, int $languageUid)
|
||||
{
|
||||
$documents = $this->apacheSolrDocumentRepository->findByTypeAndPidAndUidAndLanguageId($type, $uid, $pageId, $languageUid);
|
||||
$this->view->assign('documents', $documents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the configured Solr server can be reached and provides a
|
||||
* flash message according to the result of the check.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function collectConnectionInfos()
|
||||
{
|
||||
$connectedHosts = [];
|
||||
$missingHosts = [];
|
||||
$invalidPaths = [];
|
||||
|
||||
/* @var Path $path */
|
||||
$path = GeneralUtility::makeInstance(Path::class);
|
||||
$connections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
|
||||
|
||||
if (empty($connections)) {
|
||||
$this->view->assign('can_not_proceed', true);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($connections as $connection) {
|
||||
$coreAdmin = $connection->getAdminService();
|
||||
$coreUrl = (string)$coreAdmin;
|
||||
|
||||
if ($coreAdmin->ping()) {
|
||||
$connectedHosts[] = $coreUrl;
|
||||
} else {
|
||||
$missingHosts[] = $coreUrl;
|
||||
}
|
||||
|
||||
if (!$path->isValidSolrPath($coreAdmin->getCorePath())) {
|
||||
$invalidPaths[] = $coreAdmin->getCorePath();
|
||||
}
|
||||
}
|
||||
|
||||
$this->view->assignMultiple([
|
||||
'site' => $this->selectedSite,
|
||||
'apiKey' => Api::getApiKey(),
|
||||
'connectedHosts' => $connectedHosts,
|
||||
'missingHosts' => $missingHosts,
|
||||
'invalidPaths' => $invalidPaths
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index action, shows an overview of the state of the Solr index
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function collectStatistics()
|
||||
{
|
||||
// TODO make time frame user adjustable, for now it's last 30 days
|
||||
|
||||
$siteRootPageId = $this->selectedSite->getRootPageId();
|
||||
/* @var StatisticsRepository $statisticsRepository */
|
||||
$statisticsRepository = GeneralUtility::makeInstance(StatisticsRepository::class);
|
||||
|
||||
// @TODO: Do we want Typoscript constants to restrict the results?
|
||||
$this->view->assign(
|
||||
'top_search_phrases',
|
||||
$statisticsRepository->getTopKeyWordsWithHits($siteRootPageId, 30, 5)
|
||||
);
|
||||
$this->view->assign(
|
||||
'top_search_phrases_without_hits',
|
||||
$statisticsRepository->getTopKeyWordsWithoutHits($siteRootPageId, 30, 5)
|
||||
);
|
||||
$this->view->assign(
|
||||
'search_phrases_statistics',
|
||||
$statisticsRepository->getSearchStatistics($siteRootPageId, 30, 100)
|
||||
);
|
||||
|
||||
$labels = [];
|
||||
$data = [];
|
||||
$chartData = $statisticsRepository->getQueriesOverTime($siteRootPageId, 30, 86400);
|
||||
foreach ($chartData as $bucket) {
|
||||
$labels[] = strftime('%x', $bucket['timestamp']);
|
||||
$data[] = (int)$bucket['numQueries'];
|
||||
}
|
||||
|
||||
$this->view->assign('queriesChartLabels', json_encode($labels));
|
||||
$this->view->assign('queriesChartData', json_encode($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Luke meta data for the currently selected core and provides a list
|
||||
* of that data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function collectIndexFieldsInfo()
|
||||
{
|
||||
$indexFieldsInfoByCorePaths = [];
|
||||
|
||||
$solrCoreConnections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
|
||||
foreach ($solrCoreConnections as $solrCoreConnection) {
|
||||
$coreAdmin = $solrCoreConnection->getAdminService();
|
||||
|
||||
$indexFieldsInfo = [
|
||||
'corePath' => $coreAdmin->getCorePath()
|
||||
];
|
||||
if ($coreAdmin->ping()) {
|
||||
$lukeData = $coreAdmin->getLukeMetaData();
|
||||
|
||||
/* @var Registry $registry */
|
||||
$registry = GeneralUtility::makeInstance(Registry::class);
|
||||
$limit = $registry->get('tx_meilisearch', 'luke.limit', 20000);
|
||||
$limitNote = '';
|
||||
|
||||
if (isset($lukeData->index->numDocs) && $lukeData->index->numDocs > $limit) {
|
||||
$limitNote = '<em>Too many terms</em>';
|
||||
} elseif (isset($lukeData->index->numDocs)) {
|
||||
$limitNote = 'Nothing indexed';
|
||||
// below limit, so we can get more data
|
||||
// Note: we use 2 since 1 fails on Ubuntu Hardy.
|
||||
$lukeData = $coreAdmin->getLukeMetaData(2);
|
||||
}
|
||||
|
||||
$fields = $this->getFields($lukeData, $limitNote);
|
||||
$coreMetrics = $this->getCoreMetrics($lukeData, $fields);
|
||||
|
||||
$indexFieldsInfo['noError'] = 'OK';
|
||||
$indexFieldsInfo['fields'] = $fields;
|
||||
$indexFieldsInfo['coreMetrics'] = $coreMetrics;
|
||||
} else {
|
||||
$indexFieldsInfo['noError'] = null;
|
||||
|
||||
$this->addFlashMessage(
|
||||
'',
|
||||
'Unable to contact Apache Solr server: ' . $this->selectedSite->getLabel() . ' ' . $coreAdmin->getCorePath(),
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
}
|
||||
$indexFieldsInfoByCorePaths[$coreAdmin->getCorePath()] = $indexFieldsInfo;
|
||||
}
|
||||
$this->view->assign('indexFieldsInfoByCorePaths', $indexFieldsInfoByCorePaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the information for the index inspector.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function collectIndexInspectorInfo()
|
||||
{
|
||||
$solrCoreConnections = $this->solrConnectionManager->getConnectionsBySite($this->selectedSite);
|
||||
$documentsByCoreAndType = [];
|
||||
foreach ($solrCoreConnections as $languageId => $solrCoreConnection) {
|
||||
$coreAdmin = $solrCoreConnection->getAdminService();
|
||||
$documents = $this->apacheSolrDocumentRepository->findByPageIdAndByLanguageId($this->selectedPageUID, $languageId);
|
||||
|
||||
$documentsByType = [];
|
||||
foreach ($documents as $document) {
|
||||
$documentsByType[$document['type']][] = $document;
|
||||
}
|
||||
|
||||
$documentsByCoreAndType[$languageId]['core'] = $coreAdmin;
|
||||
$documentsByCoreAndType[$languageId]['documents'] = $documentsByType;
|
||||
}
|
||||
|
||||
$this->view->assignMultiple([
|
||||
'pageId' => $this->selectedPageUID,
|
||||
'indexInspectorDocumentsByLanguageAndType' => $documentsByCoreAndType
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets field metrics.
|
||||
*
|
||||
* @param ResponseAdapter $lukeData Luke index data
|
||||
* @param string $limitNote Note to display if there are too many documents in the index to show number of terms for a field
|
||||
*
|
||||
* @return array An array of field metrics
|
||||
*/
|
||||
protected function getFields(ResponseAdapter $lukeData, $limitNote)
|
||||
{
|
||||
$rows = [];
|
||||
|
||||
$fields = (array)$lukeData->fields;
|
||||
foreach ($fields as $name => $field) {
|
||||
$rows[$name] = [
|
||||
'name' => $name,
|
||||
'type' => $field->type,
|
||||
'docs' => isset($field->docs) ? $field->docs : 0,
|
||||
'terms' => isset($field->distinct) ? $field->distinct : $limitNote
|
||||
];
|
||||
}
|
||||
ksort($rows);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets general core metrics.
|
||||
*
|
||||
* @param ResponseAdapter $lukeData Luke index data
|
||||
* @param array $fields Fields metrics
|
||||
*
|
||||
* @return array An array of core metrics
|
||||
*/
|
||||
protected function getCoreMetrics(ResponseAdapter $lukeData, array $fields)
|
||||
{
|
||||
$coreMetrics = [
|
||||
'numberOfDocuments' => $lukeData->index->numDocs,
|
||||
'numberOfDeletedDocuments' => $lukeData->index->deletedDocs,
|
||||
'numberOfTerms' => $lukeData->index->numTerms,
|
||||
'numberOfFields' => count($fields)
|
||||
];
|
||||
|
||||
return $coreMetrics;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user