refactor search functionality; remove deprecated services and implement new action and task classes for improved structure and maintainability
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Search\Tasks;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use App\Containers\Search\Tasks\ExtractHtmlContentTask;
|
||||
use App\Containers\Search\Tasks\GetBreadcrumbFromHtmlTask;
|
||||
|
||||
class BuildStaticFileIndexTask
|
||||
{
|
||||
const CACHE_KEY = 'static_html_index_v2';
|
||||
const FILES_DIR = 'sveden';
|
||||
const CACHE_TTL = 86400; // 24 часа
|
||||
|
||||
public function run(): array
|
||||
{
|
||||
return Cache::remember(self::CACHE_KEY, self::CACHE_TTL, function() {
|
||||
try {
|
||||
$index = [];
|
||||
$directory = public_path(self::FILES_DIR);
|
||||
|
||||
if (!is_dir($directory)) {
|
||||
Log::error("Directory not found: {$directory}");
|
||||
return [];
|
||||
}
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile() && $this->isHtmlFile($file)) {
|
||||
$html = file_get_contents($file->getPathname());
|
||||
[$content, $breadcrumb] = app(ExtractHtmlContentTask::class)->run($html);
|
||||
if ($content !== false) {
|
||||
$breadcrumb = app(GetBreadcrumbFromHtmlTask::class)->run($breadcrumb);
|
||||
$text = $this->normalizeText(strip_tags($content));
|
||||
$index[$file->getPathname()] = [
|
||||
'title' => $this->getFirstH1Content($content),
|
||||
'content' => $text,
|
||||
'category' => $breadcrumb ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $index;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Index creation error: ' . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function isHtmlFile(\SplFileInfo $file): bool
|
||||
{
|
||||
$extension = strtolower($file->getExtension());
|
||||
return in_array($extension, ['html', 'htm']);
|
||||
}
|
||||
|
||||
protected function normalizeText(string $text): string
|
||||
{
|
||||
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$text = preg_replace('/\s+/u', ' ', $text);
|
||||
$text = trim($text);
|
||||
return Str::lower($text);
|
||||
}
|
||||
|
||||
public function getFirstH1Content(string $content): ?string
|
||||
{
|
||||
try {
|
||||
if (preg_match('/<h1[^>]*>(.*?)<\/h1>/is', $content, $matches)) {
|
||||
return $this->normalizeText($matches[1]);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to get H1: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Search\Tasks;
|
||||
|
||||
use DiDom\Document;
|
||||
|
||||
class ExtractHtmlContentTask
|
||||
{
|
||||
public function run(string $html): ?array
|
||||
{
|
||||
$document = new Document($html);
|
||||
$content = $document->first('.vikon-content');
|
||||
if ($content === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$breadcrumb = $content->first('.row');
|
||||
if ($breadcrumb !== null) {
|
||||
$content->firstInDocument('.row')->remove();
|
||||
}
|
||||
|
||||
return [$content->html(), $breadcrumb->html() ?? null];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Search\Tasks;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class FilterSearchResultsByCategoryTask
|
||||
{
|
||||
public function run(Collection $resources, ?string $category): Collection
|
||||
{
|
||||
if ($category === null || $category === "All") {
|
||||
return $resources;
|
||||
} else {
|
||||
return $resources->where('type', $category);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Search\Tasks;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class GetAvailableSearchCategoriesTask
|
||||
{
|
||||
public function run(Collection $resources): array
|
||||
{
|
||||
return $resources->pluck('type')->unique()->values()->all();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Search\Tasks;
|
||||
|
||||
use DiDom\Document;
|
||||
|
||||
class GetBreadcrumbFromHtmlTask
|
||||
{
|
||||
public function run(string $html): ?string
|
||||
{
|
||||
$document = new Document($html);
|
||||
$breadcrumbs = $document->first('ol.breadcrumb')?->find('li') ?? [];
|
||||
|
||||
foreach ($breadcrumbs as $index => $breadcrumb) {
|
||||
if ($index === 2) { // Индексация с 0 → третий элемент = 2
|
||||
return trim($breadcrumb->text());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Search\Tasks;
|
||||
|
||||
use DiDom\Document;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class GetStaticFileCategoriesTask
|
||||
{
|
||||
const FILES_DIR = 'sveden';
|
||||
|
||||
public function run(): array
|
||||
{
|
||||
try {
|
||||
$file = public_path(self::FILES_DIR . '/' . 'index.html');
|
||||
if (!file_exists($file)) {
|
||||
Log::error("Category index file not found: {$file}");
|
||||
return [];
|
||||
}
|
||||
|
||||
$html = file_get_contents($file);
|
||||
|
||||
$document = new Document($html);
|
||||
$dropdownMenu = $document->first('ul.dropdown-menu');
|
||||
|
||||
if ($dropdownMenu === null) {
|
||||
Log::warning("Dropdown menu not found in category index file: {$file}");
|
||||
return [];
|
||||
}
|
||||
|
||||
$links = $dropdownMenu->find('a');
|
||||
$categories = [];
|
||||
|
||||
foreach ($links as $link) {
|
||||
$category = trim($link->text());
|
||||
$categories[] = $category;
|
||||
}
|
||||
|
||||
return $categories;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error extracting categories: ' . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Search\Tasks;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class GetStaticSearchCacheStatusTask
|
||||
{
|
||||
const CACHE_KEY = 'static_html_index_v2';
|
||||
const FILES_DIR = 'sveden';
|
||||
|
||||
public function run(): array
|
||||
{
|
||||
return [
|
||||
'exists' => Cache::has(self::CACHE_KEY),
|
||||
'ttl' => Cache::get(self::CACHE_KEY.'_ttl', null),
|
||||
'driver' => config('cache.default'),
|
||||
'path' => public_path(self::FILES_DIR),
|
||||
'directory_exists' => is_dir(public_path(self::FILES_DIR))
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Search\Tasks;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class PaginateCollectionTask
|
||||
{
|
||||
public function run(Collection $resources, Request $request, int $perPage = 10): array
|
||||
{
|
||||
$currentPage = LengthAwarePaginator::resolveCurrentPage();
|
||||
|
||||
// Отрезаем нужные элементы для текущей страницы
|
||||
$currentItems = $resources->slice(($currentPage - 1) * $perPage, $perPage)->all();
|
||||
|
||||
// Создаем экземпляр LengthAwarePaginator
|
||||
$paginator = new LengthAwarePaginator($currentItems, count($resources), $perPage, $currentPage, [
|
||||
'path' => $request->url(),
|
||||
'query' => $request->query(), // Changed from $request->query to $request->query()
|
||||
]);
|
||||
|
||||
$nextPage = $paginator->hasMorePages() ? $paginator->currentPage() + 1 : null;
|
||||
$prevPage = $paginator->onFirstPage() ? null : $paginator->currentPage() - 1;
|
||||
|
||||
return [
|
||||
'paginator' => $paginator,
|
||||
'next_page' => $nextPage,
|
||||
'prev_page' => $prevPage
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Search\Tasks;
|
||||
|
||||
class PaginateSearchResultsTask
|
||||
{
|
||||
public function run(array $results, int $page, int $perPage, ?array $categories): array
|
||||
{
|
||||
$total = count($results);
|
||||
$lastPage = max(1, ceil($total / $perPage));
|
||||
$page = max(1, min($page, $lastPage));
|
||||
|
||||
$offset = ($page - 1) * $perPage;
|
||||
$paginatedResults = array_slice($results, $offset, $perPage);
|
||||
|
||||
return [
|
||||
'data' => $paginatedResults,
|
||||
'meta' => [
|
||||
'current_page' => $page,
|
||||
'total' => $total,
|
||||
'per_page' => $perPage,
|
||||
'last_page' => $lastPage
|
||||
],
|
||||
'categories' => $categories
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Search\Tasks;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class SortAndHighlightSearchResultsTask
|
||||
{
|
||||
public function run(Collection $data, string $searchRequest): array
|
||||
{
|
||||
$sortedData = [];
|
||||
|
||||
foreach ($data as $item) {
|
||||
$searchData = $item['search_data'] ?? '';
|
||||
$matches = $this->getMatches($searchData, $searchRequest);
|
||||
$sortedData[$item['type']][] = [
|
||||
'data' => $item,
|
||||
'matches' => $matches,
|
||||
'tag' => $item['type']
|
||||
];
|
||||
}
|
||||
|
||||
return $sortedData;
|
||||
}
|
||||
|
||||
private function getMatches(string $haystack, string $needle): array
|
||||
{
|
||||
$matches = [];
|
||||
$offset = 0;
|
||||
if (($offset = mb_strpos($haystack, $needle, $offset, 'UTF-8')) !== false) {
|
||||
for ($i = 0; 1 > count($matches); $i++) {
|
||||
$left = max(0, $offset - 50);
|
||||
$right = min(mb_strlen($haystack, 'UTF-8'), $offset + 100);
|
||||
$excerpt = mb_substr($haystack, $left, $right - $left, 'UTF-8');
|
||||
$matches[] = $excerpt;
|
||||
}
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user