Changes
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Filament\Domain\Posts;
|
||||
|
||||
use App\Enums\PostStatus;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PostDataProcessor
|
||||
{
|
||||
/**
|
||||
* Обрабатывает данные перед созданием поста.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function process(array $data): array
|
||||
{
|
||||
// Удаляем ненужные данные
|
||||
unset($data['publication']);
|
||||
|
||||
// Устанавливаем текст для предпросмотра
|
||||
$data['preview_text'] = $this->setPreviewText($data);
|
||||
|
||||
// Устанавливаем время публикации
|
||||
$data['publish_at'] = $this->setPublishDateTime($data['publish_setting'], $data['status']);
|
||||
unset($data['publish_setting']);
|
||||
|
||||
// Генерируем данные для поиска
|
||||
$data['search_data'] = $this->generateSearchData($data['content']);
|
||||
|
||||
// Рассчитываем время чтения
|
||||
$data['reading_time'] = $this->calculateReadingTime($data['search_data']);
|
||||
|
||||
// Устанавливаем ID текущего пользователя
|
||||
$data['user_id'] = auth()->id();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает текст для предпросмотра.
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
private function setPreviewText(array $data): string
|
||||
{
|
||||
$rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']);
|
||||
if ($rowData === null) {
|
||||
$rowData = $this->getFirstBlockByName('paragraph', $data['content']);
|
||||
}
|
||||
|
||||
$previewText = $rowData ? html_entity_decode(strip_tags($rowData['data']['content'])) : '';
|
||||
return Str::limit($previewText, 160);
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает время публикации.
|
||||
*
|
||||
* @param array $publishSetting
|
||||
* @param string $status
|
||||
* @return Carbon|null
|
||||
*/
|
||||
private function setPublishDateTime(array $publishSetting, string|PostStatus $status): ?Carbon
|
||||
{
|
||||
if ($publishSetting['publish_after'] === true) {
|
||||
return Carbon::parse($publishSetting['publish_at']);
|
||||
}
|
||||
|
||||
if ($status === PostStatus::PUBLISHED) {
|
||||
return Carbon::now();
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует данные для поиска.
|
||||
*
|
||||
* @param array $content
|
||||
* @return string
|
||||
*/
|
||||
private function generateSearchData(array $content): string
|
||||
{
|
||||
$result = "";
|
||||
foreach ($content as $block) {
|
||||
$result .= $this->getDataFromBlocks($block);
|
||||
}
|
||||
|
||||
// Удаляем лишние пробелы и переносы строк
|
||||
$result = preg_replace('/\s+/', ' ', $result);
|
||||
$result = trim($result);
|
||||
|
||||
return strtolower($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает данные из блоков контента.
|
||||
*
|
||||
* @param array $block
|
||||
* @return string
|
||||
*/
|
||||
private function getDataFromBlocks(array $block): string
|
||||
{
|
||||
$data = "";
|
||||
switch ($block['type']) {
|
||||
case 'paragraph':
|
||||
$data .= strip_tags($block['data']['content']) . " ";
|
||||
break;
|
||||
case 'heading':
|
||||
$data .= strip_tags($block['data']['content']) . " ";
|
||||
break;
|
||||
case 'files':
|
||||
foreach ($block['data']['file'] as $file) {
|
||||
$data .= $file['title'] . " ";
|
||||
}
|
||||
break;
|
||||
case 'person':
|
||||
$data .= $block['data']['name'] . " ";
|
||||
break;
|
||||
case 'stepper':
|
||||
$data .= $block['data']['step_name'] . " ";
|
||||
foreach ($block['data']['steps'] as $step) {
|
||||
$data .= $step['title'] . " ";
|
||||
$data .= strip_tags($step['content']) . " ";
|
||||
}
|
||||
break;
|
||||
case 'tabs':
|
||||
foreach ($block['data']['tab'] as $item) {
|
||||
foreach ($item['content'] as $block) {
|
||||
$data .= $this->getDataFromBlocks($block);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Рассчитывает время чтения.
|
||||
*
|
||||
* @param string $text
|
||||
* @return int
|
||||
*/
|
||||
private function calculateReadingTime(string $text): int
|
||||
{
|
||||
$wordCount = str_word_count($text, 0, "АаБбВвГгДдЕеЁёЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯя");
|
||||
$wordsPerMinute = 120; // Средняя скорость чтения
|
||||
return max(1, round($wordCount / $wordsPerMinute));
|
||||
}
|
||||
|
||||
/**
|
||||
* Находит первый блок по имени.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $content
|
||||
* @return array|null
|
||||
*/
|
||||
private function getFirstBlockByName(string $name, array $content): ?array
|
||||
{
|
||||
foreach ($content as $block) {
|
||||
if ($block['type'] === $name) {
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Находит блок по SEO-активности.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $content
|
||||
* @return array|null
|
||||
*/
|
||||
private function getBlockBySeoActiveState(string $name, array $content): ?array
|
||||
{
|
||||
foreach ($content as $block) {
|
||||
if ($block['type'] === $name && ($block['data']['seo_active'] ?? false)) {
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Filament\Domain\Posts;
|
||||
|
||||
use App\Filament\Resources\PostResource;
|
||||
use App\Models\Post;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Filament\Notifications\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
class PostNotificationService
|
||||
{
|
||||
/**
|
||||
* Отправляет уведомления редакторам о новом посте.
|
||||
*
|
||||
* @param Post $post
|
||||
* @return void
|
||||
*/
|
||||
public function send(Post $post): void
|
||||
{
|
||||
// Находим всех пользователей с ролью "editor"
|
||||
$editors = Role::findByName('editor')->users;
|
||||
|
||||
// Отправляем уведомление каждому редактору
|
||||
foreach ($editors as $editor) {
|
||||
Notification::make()
|
||||
->title('Новость на проверку')
|
||||
->body('Новость "' . $post->title . '" нуждается в проверке')
|
||||
->actions([
|
||||
Action::make('view')
|
||||
->label('Проверить')
|
||||
->button()
|
||||
->markAsRead()
|
||||
->url(PostResource::getUrl('edit', ['record' => $post])),
|
||||
])
|
||||
->sendToDatabase($editor); // Отправляем уведомление конкретному редактору
|
||||
}
|
||||
}
|
||||
|
||||
public function sendSuccessNotification(Post $post): void
|
||||
{
|
||||
$user = User::find($post->user_id);
|
||||
|
||||
Notification::make()
|
||||
->title('Ваша новость опубликована')
|
||||
->body('Новость "' . $post->title . '" опубликована')
|
||||
->actions([
|
||||
Action::make('view')
|
||||
->label('Смотреть')
|
||||
->button()
|
||||
->markAsRead()
|
||||
->url(route('client.post.show', $post->slug)),
|
||||
])
|
||||
->sendToDatabase($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправляет уведомление об отклонении.
|
||||
*
|
||||
* @param Post $post
|
||||
* @return void
|
||||
*/
|
||||
public function sendDeniedNotification(Post $post): void
|
||||
{
|
||||
$user = User::find($post->user_id);
|
||||
|
||||
Notification::make()
|
||||
->title('Ваша новость отклонена :(')
|
||||
->body('Новость "' . $post->title . '" была отклонена')
|
||||
->actions([
|
||||
Action::make('view')
|
||||
->label('Смотреть')
|
||||
->button()
|
||||
->markAsRead()
|
||||
->url(PostResource::getUrl('edit', ['record' => $post])),
|
||||
])
|
||||
->sendToDatabase($user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Filament\Domain\Posts;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PostSeoGenerator
|
||||
{
|
||||
/**
|
||||
* Генерирует SEO-данные для поста.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function generate(array $data): array
|
||||
{
|
||||
return [
|
||||
'title' => $this->extractSeoTitle($data),
|
||||
'description' => $this->extractSeoDescription($data['content']),
|
||||
'image' => $this->extractSeoImage($data),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает SEO-заголовок.
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
private function extractSeoTitle(array $data): string
|
||||
{
|
||||
return $data['title'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает SEO-описание.
|
||||
*
|
||||
* @param array $content
|
||||
* @return string
|
||||
*/
|
||||
private function extractSeoDescription(array $content): string
|
||||
{
|
||||
$rowData = $this->getBlockBySeoActiveState('paragraph', $content);
|
||||
if ($rowData === null) {
|
||||
$rowData = $this->getFirstBlockByName('paragraph', $content);
|
||||
}
|
||||
|
||||
$description = $rowData ? html_entity_decode(strip_tags($rowData['data']['content'])) : '';
|
||||
return Str::limit(htmlspecialchars($description, ENT_QUOTES, 'UTF-8'), 160);
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает SEO-изображение.
|
||||
*
|
||||
* @param array $data
|
||||
* @return string|null
|
||||
*/
|
||||
private function extractSeoImage(array $data): ?string
|
||||
{
|
||||
return $data['preview'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Находит первый блок по имени.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $content
|
||||
* @return array|null
|
||||
*/
|
||||
private function getFirstBlockByName(string $name, array $content): ?array
|
||||
{
|
||||
foreach ($content as $block) {
|
||||
if ($block['type'] === $name) {
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Находит блок по SEO-активности.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $content
|
||||
* @return array|null
|
||||
*/
|
||||
private function getBlockBySeoActiveState(string $name, array $content): ?array
|
||||
{
|
||||
foreach ($content as $block) {
|
||||
if ($block['type'] === $name && ($block['data']['seo_active'] ?? false)) {
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setPreviewText(array $data): ?string
|
||||
{
|
||||
$rowData = $this->getBlockBySeoActiveState('paragraph', $data['content']);
|
||||
if ($rowData === null) {
|
||||
$rowData = $this->getFirstBlockByName('paragraph', $data['content']);
|
||||
}
|
||||
|
||||
if ($rowData !== null) {
|
||||
$previewText = html_entity_decode(strip_tags($rowData['data']['content']));
|
||||
return Str::limit($previewText, 160);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Filament\Domain\Posts;
|
||||
|
||||
use App\Dto\MainSliderDTO;
|
||||
use App\Models\MainSlider;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PostSliderService
|
||||
{
|
||||
public function __construct(
|
||||
readonly private MainSliderDTO $dto,
|
||||
readonly private string $postSlug,
|
||||
){}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
try {
|
||||
MainSlider::create([
|
||||
'title' => $this->dto->title,
|
||||
'content' => $this->dto->content,
|
||||
'image' => $this->dto->image,
|
||||
'link' => parse_url(route('client.post.show', $this->postSlug), PHP_URL_PATH),
|
||||
'link_text' => $this->dto->link_text,
|
||||
'is_active' => $this->dto->is_active,
|
||||
'color_theme' => $this->dto->color_theme,
|
||||
'start_time' => $this->dto->start_time,
|
||||
'end_time' => $this->dto->end_time,
|
||||
]);
|
||||
|
||||
Log::info('MainSlider created successfully', ['postSlug' => $this->postSlug]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to create MainSlider', ['error' => $e->getMessage()]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function update(): void
|
||||
{
|
||||
try {
|
||||
MainSlider::update([
|
||||
'title' => $this->dto->title,
|
||||
'content' => $this->dto->content,
|
||||
'image' => $this->dto->image,
|
||||
'link' => parse_url(route('client.post.show', $this->postSlug), PHP_URL_PATH),
|
||||
'link_text' => $this->dto->link_text,
|
||||
'is_active' => $this->dto->is_active,
|
||||
'color_theme' => $this->dto->color_theme,
|
||||
'start_time' => $this->dto->start_time,
|
||||
'end_time' => $this->dto->end_time,
|
||||
]);
|
||||
|
||||
Log::info('MainSlider updated successfully', ['postSlug' => $this->postSlug]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to update MainSlider', ['error' => $e->getMessage()]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Filament\Domain\Posts;
|
||||
|
||||
use App\Enums\PostStatus;
|
||||
use App\Jobs\CreateVkPost;
|
||||
use App\Models\Post;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class VkPostPublisher
|
||||
{
|
||||
/**
|
||||
* Публикует пост в социальных сетях.
|
||||
*
|
||||
* @param array $settings
|
||||
* @param Post $post
|
||||
* @return void
|
||||
*/
|
||||
public function publish(array $settings, Post $post): void
|
||||
{
|
||||
if ($post->status === PostStatus::PUBLISHED) {
|
||||
if ($settings['vk']) {
|
||||
$text = $this->generateContentForVk($post->content);
|
||||
$images = $this->generateImageLinksForVk($post->images);
|
||||
$publishDate = $post->publish_at > now() ? $post->publish_at->timestamp : null;
|
||||
|
||||
dispatch(new CreateVkPost($post->title, $text, $images, $post->id, $publishDate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует текстовый контент для ВКонтакте.
|
||||
*
|
||||
* @param array $content
|
||||
* @return string
|
||||
*/
|
||||
private function generateContentForVk(array $content): string
|
||||
{
|
||||
$text = '';
|
||||
foreach ($content as $block) {
|
||||
switch ($block['type']) {
|
||||
case 'paragraph':
|
||||
$text .= strip_tags($block['data']['content']) . "\n\n";
|
||||
break;
|
||||
case 'heading':
|
||||
$text .= strip_tags($block['data']['content']) . "\n\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return trim($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует ссылки на изображения для ВКонтакте.
|
||||
*
|
||||
* @param array $images
|
||||
* @return array
|
||||
*/
|
||||
private function generateImageLinksForVk(array $images): array
|
||||
{
|
||||
return array_map(function ($file) {
|
||||
return Storage::url($file); // Генерируем полный URL для изображения
|
||||
}, $images);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user